搜尋此網誌

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.

2015年8月26日 星期三

【Android】add (vertical) divider/seam to a horizontal LinearLayout?

ref:Add devider or seams between each layouts

【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"
才可以達到想要的效果。

2015年8月22日 星期六

【Note】Personal Blog is worth reading

http://www.lightskystreet.com/
http://frank-zhu.github.io/tools/2014/08/23/android-tools-and-plugin/

【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


【工具】Json Parser Online

前面有貼過JSON Pretty Print 這個工具



有更簡潔版本

Json Parser Online

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



2015年8月20日 星期四

【Android】Material design

introduce:
https://www.google.com/design/spec/material-design/introduction.html

icon download:
https://www.google.com/design/icons/index.html

【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();
        }
    }