2015年8月30日 星期日

【Android】Volley - 好用官方的httpclient library

Volley是Gogle在Google I/O 2013所發表的網路框架,主要是為了加強Android網路應用的效能。推出Volley的原因是:
  • 目前廣泛使用的HttpURLConnectionHttpClient有已知的問題和bug是不易修復的,甚至HttpClient可能不再被維護
  • 目前在Android上抓取網路資料的標準作法,是需要透過執行緒,再把結果送回主程序,過程稍嫌複雜
可說是集中非同步的網路通訊優點,同時提供了字串、JSON、圖片三種請求方式。內部再把Http和執行緒封裝起來。還提供Cache,使得在performance上改善不少。
Google建議使用Volley來存取資料量不大且頻繁的網路通訊,Volley在大量資料的傳輸上,效能很差。

目前沒有Google官方提供的Volley封裝檔,建議如下步驟:

String、Json、Image三個的寫法差不多,都是先從URL連結啟動請求之後,將請求加到Queue去處理(請注意,這是非同步的),若成功則在onResponse事件中取得回傳值,若失敗則會在onErrorResponse得到錯誤訊息。

for Gradle

compile 'com.mcxiaoke.volley:library:1.0.19'

Premission (AndroidManifest.xml)

別忘了,Volley 是需要是用網路的,所以記得加上這一行。


<uses-permission android:name="android.permission.INTERNET" />

Example

RequestQueue mQueue = Volley.newRequestQueue(context);

  • StringRequest 
String url ="http://www.google.com";
StringRequest stringRequest = new StringRequest(url,new Response.Listener<String>() {
  @Override
  public void onResponse(String response) {
   Log.d("TAG", response);
  }
 }, new Response.ErrorListener() {
  @Override
  public void onErrorResponse(VolleyError error) {
   Log.e("TAG", error.getMessage(), error);
  }
 });
 
mQueue.add(stringRequest);
  • JsonRequest
String url ="http://my-json-feed";
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, "UTF-8",
  new Response.Listener<JSONObject>() {
   @Override
   public void onResponse(JSONObject response) {
    Log.d("TAG", response.toString());
   }
  }, new Response.ErrorListener() {
   @Override
   public void onErrorResponse(VolleyError error) {
    Log.e("TAG", error.getMessage(), error);
   }
  });
 
mQueue.add(jsonObjectRequest);
  • Image Request
String url="http://i.imgur.com/rwTYZzF.png";
ImageRequest imageRequest = new ImageRequest(
  url, new Response.Listener<Bitmap>() {
  @Override
  public void onResponse(Bitmap response) {
   imageView.setImageBitmap(response);
  }
 }, 0, 0, null, new Response.ErrorListener() {
   @Override
   public void onErrorResponse(VolleyError error) {
    imageView.setImageResource(R.drawable.default_image);
   }
  });
mQueue.add(imageRequest);

可稍微改寫一下JsonRequest使得資料更好取得
String url = "http://my-json-feed";
        // 取得資料並編碼
        JsonArrayPostRequest jRequest = new JsonArrayPostRequest(url,
                new Response.Listener<JSONArray>() {

                    public void onResponse(JSONArray response) {

                        //... easy to do something

                    }
                }, new Response.ErrorListener() {

            public void onErrorResponse(VolleyError error) {
                VolleyLog.d(TAG, "Error: " + error.getMessage());

            }
        });
        // 設定執行為最高優先權
  jRequest.setPriority(Request.Priority.HIGH);
        
  mQueue.add(jRequest);

ref:
http://www.androidhive.info/2014/05/android-working-with-volley-library-1/
http://code.tutsplus.com/tutorials/an-introduction-to-volley--cms-23800
https://github.com/mcxiaoke/android-volley
http://blog.csdn.net/guolin_blog/article/details/17482095

2015年8月27日 星期四

【工具】刪除資料夾或檔案出現「找不到此項目」

在筆記本上輸入:

DEL /F /A /Q \\?\%1
RD /S /Q \\?\%1

接著,將檔案名儲存為 .bat的副檔名

將無法刪除的檔案或是資料夾拖曳到此bat圖示上即可

【Android】runOnUiThread vs Looper.getMainLooper().post in Android

ref:http://stackoverflow.com/a/13974916

The following behaves the same when called from background threads
via Looper.getMainLooper()
    Runnable task = getTask();
    new Handler(Looper.getMainLooper()).post(task);
via Activity#runOnUiThread()
    Runnable task = getTask();
    runOnUiThread(task);
The only difference is when you do that from the UI thread since
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
will check if the current Thread is already the UI thread and then execute it directly. Posting it as a message will delay the execution until you return from the current UI-thread method.
There is also a third way to execute a Runnable on the UI thread which would be View#post(Runnable) - this one will always post the message even when called from the UI thread. That is useful since that will ensure that the View has been properly constructed and has a layout before the code is executed.

【Android】How can I add the new “Floating Action Button” between two widgets/layouts

ref:add the new “Floating Action Button” between two widgets/layouts

Best practice:
  • Add compile 'com.android.support:design:22.2.0' to gradle file
  • Use CoordinatorLayout as root view.
  • Add layout_anchorto the FAB and set it to the top view
  • Add layout_anchorGravity to the FAB and set it to: bottom|right|end

【Android】Android Support Design TabLayout: Gravity Center and Mode Scrollable

簡單來說,問題是

設定tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

手機直立顯示,Tab在畫面上看起來很正常,超出螢幕的寬度,可以用scroolable解。



當手機橫置顯示時,Tab的總寬度小於螢幕寬度太多,造成視覺上很奇怪,如何解?

答案:

As I didn't find why does this behaviour happen I have used the following code:
float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
    tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
Basically, I have to calculate manually the width of my tabLayout and then I set the Tab Mode depending on if the tabLayout fits in the device or not.
The reason why I get the size of the layout manually is because not all the tabs have the same width in Scrollable mode, and this could provoke that some names use 2 lines as it happened to me in the example.

在不同顯示下,使用不同的顯示方式。
上述解答,應需加
app:tabGravity="fill"
才可以達到想要的效果。

【Android】AsyncTask cant be executed twice

問題:
Cannot execute task: the task has already been executed (a task can be executed only once)
http://stackoverflow.com/questions/19345118/async-task-cant-be-executed-twice

解答:
As the exception itself explains, you cannot execute an AsyncTask more than once, unless you create a new instance of it and call .execute.
For example:
async = new AsyncTask();
async.execute();
*in order to execute more than once, you need to re-create the instance (using new) the number of times you want to execute it.
但有人說沒作用

問題:
Android restart AsyncTask
http://stackoverflow.com/questions/11586704/android-restart-asynctask

解答:
改成使用Thread 和 HandleMessage實作
Thank you for all your comments they helped a lot. I decided to do away with the AsyncTask. I ended using a normal runnable Thread and using Handlers to post messages back to the UI thread. here is the code:
        // Start thread here only for IsSocketConnected
        new Thread(new Runnable() {

            public void run() {

                //Add your code here..
                IsSocketConnected();

            }
        }).start();


// handler that deals with updating UI
public Handler myUIHandler = new Handler()
{
    @Override
    public void handleMessage(Message msg)
    {
        if (msg.what == Bluetooth.STATE_CONNECTED)
        {
            //Update UI here...
            Log.d(TAG, "Connected");




            // Discover available devices settings and create buttons
            CreateButtons(btnList);

        } else if(msg.what == Bluetooth.STATE_NONE) {

            Log.d(TAG, "NOT Connected");
        }
}
// in the IsSocketConnected() I call this 
Message theMessage = myUIHandler.obtainMessage(Bluetooth.STATE_CONNECTED);
myUIHandler.sendMessage(theMessage);//Sends the message to the UI handler.
This is working so far. Thank you again. Hope this helps someone


2015年8月21日 星期五

【Android】實作SearchView / Search widget

紀錄一下
http://www.mysamplecode.com/2012/07/android-listview-custom-layout-filter.html
http://stackoverflow.com/questions/17720481/how-could-i-filter-the-listview-using-baseadapter
http://stackoverflow.com/questions/5713653/how-to-get-the-events-of-searchview-in-android
http://www.tutorialsbuzz.com/2014/08/filter-custom-listviewbaseadapter.html
--------------------------------------------------------

2015/08/21 22:54 整理
http://www.mysamplecode.com/2012/11/android-expandablelistview-search.html
http://www.cnblogs.com/over140/archive/2010/11/25/1887892.html
看得不少資料  感覺大部分都是需要 extends Filter來實作過濾listview的search功能,但感覺好像太複雜了,對我來說...
今天試了另一種方法,code比較簡潔,原理和上面的都差不多,也是想要的結果。就分享大概的步驟(http://developer.android.com/training/search/setup.html),如下:

res/menu/menu.xml
新增search item
<item
        android:id="@+id/action_search"
        android:icon="@drawable/ic_search"
        android:imeOptions="actionSearch"
        android:inputType="textCapWords"
        android:title="search"
        android:orderInCategory="90"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="ifRoom|collapseActionView"/>
其中
  • orderInCategory="90" 
    • 因為仍有其他menu item,排在Search右邊,所以先設90(由左至右;數字由小至大)
  • ifRoom 只要actionbar有空間,item就會顯示出來。
  • collapseActionView item使用時會展開,不用時會縮起來。
MainActivity
在畫面上顯示Menu item,在onCreateOptionsMenu的function 加上
getMenuInflater().inflate(R.menu.menu_main, menu);
確保search item會出現在menu上,但這時候search還是一樣沒有功能,需要繼續定義Search View的行為。

res/xml/searchable.xml
這步蠻重要,沒加這個,害我白做工好幾回。
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
            android:label="@string/msg_search"
            android:hint="@string/msg_search_help"/>
參考網路上的解釋:
一個Searchable Configuration至少包含一個android:label,其屬性要和你的 Android manifest(Android清單)中 <application>或<activity>元素的android:label屬性具有相同的值。

AndroidMainifest.xml
<meta-data
     android:name="android.app.searchable"
     android:resource="@xml/searchable" />
定義meta-data告知應用程式可以到res/xml/searchable.xml找到他。
<intent-filter>
     <action android:name="android.intent.action.SEARCH"/>
</intent-filter>
宣告activity須接受 action_SEARCH intent

MainActivity
SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
通過調用setSearchableInfo(SearchableInfo)方法來關聯搜索配置(the searchable configuration )和SearchView:

通過調用getSearchableInfo()方法可從已創建的可搜索的XML配置文件( the searchable configuration XML file)中獲得SearchableInfo對象。 

當可搜索的配置(searchable configuration)正確地與你的SearchView 相關聯時,用戶提交一個搜索查詢後,SearchView可以通過ACTION_SEARCH intent,啟動一個 activity。現在,你需要一個可以篩選(filter)這個intent和處理搜索查詢的活動。



最後和上面網頁的例子都有些不一樣。
ViewItemAdapter

private LayoutInflater mInflater;
    ArrayList<AirSiteObject> data;
    ArrayList<AirSiteObject> oldData;

    public ViewItemAdapter(Context view, ArrayList<AirSiteObject> arrayList) {
        mInflater = LayoutInflater.from(view);
        data = arrayList;
        oldData = new ArrayList<AirSiteObject>();
        oldData.addAll(data);
    }

data 是放置 listview的layout,過濾後的結果也用它呈現。
olddata 放置原本的資料。

額外增加兩個method
public void setSearchPattern(String pattern) {
        filterListByPattern(pattern.trim());
        this.notifyDataSetChanged();
    }

    private void filterListByPattern(String searchPattern) {
        data.clear();
        for (AirSiteObject info : oldData) {
            boolean add = true;
            do {
                if (searchPattern == null || searchPattern.equals("")) {
                    break;
                }
                if (info.getSiteName().contains(searchPattern)) {
                    break;
                }
                if (info.getCounty().contains(searchPattern)) {
                    break;
                }
                add = false;
            } while (false);
            if (add) {
                data.add(info);
            }
        }
    }

註:過濾的是中文,且資料內不可能會有空格產生,所以先用trim()把空格都濾掉。
  • 當search pattern是空的或是null時,就等於呈現的是原始資料。
  • 當pattern過濾條件符合,就加進來。

MainActivity/OnCreateOptionMenu
增加setOnQueryTextListener,但為了美觀,把整個method都show出來。

@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);

        SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
        final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String s) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String s) {
                mAdapter.setSearchPattern(s);
                return true;
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

其他res內的小bug改一改,應該就可以跑了。

先記錄到這邊,希望下次使用到這個功能,我才能看得懂在寫什麼。


ref: http://developer.android.com/training/search/setup.html

為了讓code版型不要跑掉,光這樣就花了1個半小時在寫這篇,累~

【Android】ListView進階版 --- RecyclerView

先記錄  有時間再看
http://lp43.blogspot.tw/2014/08/recyclerviewandroid-studio.html
--------------------------------------------------------------------------------
2015/10/10更新

詳細實作過程
【Android】實作RecyclerView + ToolBar + SearchView



【Android】實作從html讀取JSON 〈2〉

最近想測試從網頁上讀Json並在Android手機上

舊方法:【Java】實作從html讀取JSON (應該是說在Android API 22之後Apache HTTP Client Removal,Http Get /post的方法被捨棄 )

新方法:以HttpURLConnection實作讀取

因為是要顯示在Android 手機上,那種耗時的網路連線、資料整理的動作並不能在main thread中做處理,所以需要額外開一條Thread在background做處理或是以AsyncTask。
在這裡使用AsyncTask來處理相關動作。(更多關於AsyncTask)

較重要的程式碼,如下:

AndroidManifest.xml
須加上
<uses-permission android:name="android.permission.INTERNET" />
允許使用網路
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
</uses-permission>
允許檢查網路狀態

FetchTask.java
public class FetchTask extends AsyncTask<String,Void,String> {

    interface OnFetchListener {
        public void OnAirDataFetchFinished();
    }

    private OnFetchListener onFetchListener;

    public void setOnFetchListener(OnFetchListener listener) {
        onFetchListener =listener;
    }



    @Override
    protected String doInBackground(String... params) {
        DataFetcher.getInstance().fetchAirData();
        return  DataFetcher.getInstance().getResult();
    }

    @Override
    protected void onPostExecute(String s) {
        onFetchListener.OnAirDataFetchFinished();
    }
}
DataFetcher.java
public class DataFetcher {
    private StringBuilder airData = new StringBuilder();
    private String path = "http://......";
    private ArrayList<AirSiteObject> mItems;

    private DataFetcher() {

    }

    private static DataFetcher mFetcher;

    public static DataFetcher getInstance() {
        if (null == mFetcher) {
            mFetcher = new DataFetcher();
        }
        return mFetcher;
    }

    public void fetchAirData() {
        HttpURLConnection httpURLConnection = null;
        try {
            URL url = new URL(path);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.connect();
            int status = httpURLConnection.getResponseCode();
            switch (status) {
                case 200:
                case 201:
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "UTF-8"));
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        airData.append(line + "\n");
                    }
                    bufferedReader.close();

            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (httpURLConnection != null) {
                httpURLConnection.disconnect();

            }
        }

    }

    public String getResult() {
        return airData.toString();
    }

    //JSONParser
    public ArrayList<AirSiteObject> GetAirData(String result) {
        mItems =new ArrayList<AirSiteObject>();
        JSONObject obj;
        try {
            JSONArray jsonArray = new JSONArray(result);
            for (int i = 0; i < jsonArray.length(); i++) {
                obj = jsonArray.getJSONObject(i);
                mItems.add(new AirSiteObject(obj));
            }

        } catch (JSONException e) {
            Log.e("MYAPP", "unexpected JSON exception", e);
        }
        return mItems;
    }
}
MainActivity.java
private FetchTask mFetchAirDataTask;
記得要
implements FetchTask.OnFetchListener
onCraete method
mFetchAirDataTask = new FetchTask();mFetchAirDataTask.setOnFetchListener(this);executeTask();

判斷網路是否正常
private void executeTask() {
    if (ToolsHelper.isNetworkAvailable(this)) {
        mFetchAirDataTask.execute();    } else {
        Toast.makeText(this, "未偵測到網路,請確認網路狀況。", Toast.LENGTH_LONG).show();    }
}

實作OnFetchListener介面的OnAirDataFetchFinished method
@Override
    public void OnAirDataFetchFinished() {
        String result = DataFetcher.getInstance().getResult();
        listView.setAdapter(new ViewItemAdapter(this, DataFetcher.getInstance().GetAirData(result)));
        if (DataFetcher.getInstance().GetAirData(result).size() == 0) {
            Toast.makeText(this, "資料讀取失敗,請稍候再重試。", Toast.LENGTH_LONG).show();
        }
    }