編輯:關於Android編程
從Android 3.0開始引進了loader(加載器)技術, 在activity或者fragment中,loaders可以把異步地加載數據變得更簡單。Loaders具有以下特性:
他們對於每一個Activity和Fragment都是有效的。 他們可以提供異步加載數據的能力。 他們監視數據源,並當內容改變時傳遞當前最新的結果。 當他們因為配置的改變而重新連接的時候,他們會自動地重連到上一個loader的游標。因此,他們不需要重新查詢數據。在應用中使用loaders可能會涉及到多個類和接口。他們被匯總到了下表中:
LoaderManager
一個關聯到Activity或 Fragment的抽象類,用來管理一個或多個 Loader實例這樣可以幫助一個應用管理那些跟Activity 或者Fragment的生命周期聯系到一起的長時間運行的操作;這樣的最常見的使用就是 CursorLoader,然而應用可以自由地寫自己的加載器來加載其他類型的數據。
每一個activity或者fragment都只有一個LoaderManager。但是一個 LoaderManager可以擁有多個加載器(loaders)。
LoaderManager.LoaderCallbacks
一個用來去為客戶端和LoaderManager提供交互的回調接口。 舉個例子,你可以使用onCreateLoader() 回調方法來創建一個新的loader(加載器)。
Loader
一個執行異步的數據加載的抽象類。這是加載器的基類。你可以使用典型的CursorLoader,但是也可以實現你自己的子類。 當loaders被激活的時候,它們應該見識數據源並且當內容改變的時候傳遞最新結果。
AsyncTaskLoader
提供一個AsyncTask來執行異步加載數據的抽象loader。
CursorLoader
AsyncTaskLoader的一個子類,查詢 ContentResolver然後返回一個Cursor。這個類用標准的方式實現了 Loader的協議以此來查詢cursors, AsyncTaskLoader在後台線程中執行cursor查詢所以它不會阻塞應用的UI。 使用loader是從ContentProvider異步加載數據的最好的方式, 相對於通過fragment或activity的API來執行查詢
上表中的那些類和接口都是你將會用來在應用中實現loader的極其重要的組件。你不必在創建每一個loader的時候全部使用,但是你總是需要一個 LoaderManager的引用,用來初始化一個loader Loader類的實現,類似於CursorLoader。 下面的章節將會向您展示怎樣在應用中使用這些類和接口。
一個典型地使用了loaders的應用應該包含以下內容:
一個Activity或者Fragment。 一個LoaderManager的實例。 一個CursorLoader來載入被ContentProvider返回的數據。 或者,你可以實現自己的Loader或者AsyncTaskLoader的子類 從而從其他的資源加載數據 LoaderManager.LoaderCallbacks的一個實現 這是你創建新的loader和管理你的已經存在的loader的引用的地方 一個展示loader的數據的方法,比如一個SimpleCursorAdapter。 一個數據源,比如一個ContentProvider,當你使用一個 CursorLoader的時候。LoaderManager管理一個Activity或 Fragment范圍內的一個或多個Loader實例。每一個Activity或者Fragment都只有一個 LoaderManager。
你可以典型地初始化一個 Loader,可以在activity的 onCreate()方法,也可以在fragment的 onActivityCreated()方法中。你可以像下面一樣做這件事:
//准備loader,無論是與一個已經存在的loader重連, //還是新建一個。 getLoaderManager().initLoader(0, null, this);
initLoader()方法需要以下參數:
一個可以標識loader的唯一的ID。本例中的ID是0。 一個可選參數,當loader初始化時提供給它(在本例中是null)。 一個LoaderManager.LoaderCallbacks的實現,將會被 LoaderManager調用,用來報告loader的時間。本例中,本地類實現了 LoaderManager.LoaderCallbacks借口,所以它傳遞了一個自身的引用 this。initLoader()方法調用確保了一個loader會被初始化以及激活 它有兩種可能的後果:
如果賦予loader的ID已經存在,那麼上一個被創建的loader就會被重用 如果賦予loader的ID不存在, initLoader()就會觸發 LoaderManager.LoaderCallbacks的onCreateLoader()方法。 這裡就是你實例化並返回一個新loader的地方。 更多的討論,請參看onCreateLoader章節。無論在哪一種情況中,傳入的LoaderManager.LoaderCallbacks 實現都會跟loader綁定在一起,它將會在loader狀態改變時被調用。如果在本次調用時,調用者處於開始狀態,並且所請求的loader已經存在並產生了數據,那麼系統就會立馬調用 onLoadFinished() (即在initLoader()過程中), 所以你必須為這種情況做好准備。更多關於這個回調方法的討論,請參看 onLoadFinished。
請注意,initLoader() 方法返回被創建的Loader,但是你不必保留它的引用, LoaderManager會自動管理loader的生命, LoaderManager 會在必要的時候啟動和終止,以及維護loader的狀態和它關聯的內容。這就意味著,你幾乎不用和loader進行直接交互 (尋求使用loader方法來調整loader行為的例子,請參看 LoaderThrottle實例)。 你最常用的手段是當特定事件發生時,使用LoaderManager.LoaderCallbacks方法來介入到加載過程中。 請參看Using the LoaderManager Callbacks。
當你像上面展示的那樣使用initLoader()的時候, 如果有的話,它會使用已存在的帶有標識ID的loader。 如果沒有,它會創建一個。但是有時你會想要丟掉舊的數據,開始新的過程。
為了丟棄舊的數據,你要使用restartLoader()。 例如,SearchView.OnQueryTextListener的實現在用戶查詢改變時重啟了, loader需要被重啟從而能夠使用修改過的搜索過濾進行新的查詢:
public boolean onQueryTextChanged(String newText) { //當action bar的搜索文本改變時調用。 //更新搜索過濾器,然後重啟loader用當前的過濾器 //做一次新的查詢。 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; }
LoaderManager.LoaderCallbacks是一個回調接口,它為客戶端提供與 is a callback interface LoaderManager交互的能力。
Loaders,尤其是CursorLoader,大家都希望當它被停止以後仍然可以保持數據。 這樣允許應用在activity或fragment的 onStop()和onStart()方法之間保持數據, 以至於當用戶返回到一個應用的時候,他們不必再等待數據的重新加載。 你可以使用LoaderManager.LoaderCallbacks方法 當你知道何時需要創建新的loader,以及高速應用何時停止使用loader的數據。
LoaderManager.LoaderCallbacks包括以下方法:
onCreateLoader() — 根據傳入的ID初始化並返回一個新的Loader。 onLoadFinished() — 當一個之前被創建的loader已經結束加載數據的時候會調用此方法。 onLoaderReset() — 當一個之前創建的loader被重置的時候會調用此方法,這樣會導致它的數據不可用。在下面的章節中會更詳細地描述這些方法的細節
onCreateLoader
當你試圖操作一個loader的時候,(例如通過initLoader()), 會檢查被賦予唯一ID的loader是否存在。如果不存在,它會觸發LoaderManager.LoaderCallbacks的onCreateLoader()方法。 這是你創建新loader的地方。一般來說被創建的都是CursorLoader,但是你可以實現你自己的Loader子類。
在本例中,onCreateLoader() 回調方法創建了一個CursorLoader。你必須使用它的構造方法建造這個 CursorLoader,構造方法需要向 which ContentProvider執行一次查詢的完整信息作為參數。它還需要:尤其地,它還需要:
* uri — 要獲取的內容的URI。
* projection — 返回的列組成的列表,傳入null將會返回所有列,但是效率很低。
* selection — 一個聲明返回哪些行的過濾器,被格式化成類似SQL中WHERE子句的形式(除了沒有WHERE自己)。傳入null將會返回給定URI的所有行。
* selectionArgs — 你可能在Selection中包含一些‘?’,他們將會被selectionArgs的值給替換掉,順序與它們在selection中出現的順序一致。 這些值被約束為String類型
* sortOrder — 怎樣給這些行排順序,被格式化為類似SQL中ORDER BY子句的形式(除了沒有ORDER自己)。傳入null 將會使用默認的排序方式,可能是沒有順序。
例如:
//如果不是null,這就是當前的用戶提供的過濾器。 String mCurFilter; ... public LoaderonCreateLoader(int id, Bundle args) { //當新的Loader需要被創建的時候調用此方法。 //本例僅有一個Loader,所以不必關心ID的問題。 //首先,根據我們是否正在過濾, //選擇base URI來使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //現在創建並返回一個CursorLoader, //它會創建一個用來顯示數據的Cursor。 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
onLoadFinished
當上一個被創建的loader已經結束數據加載的時候調用此方法。這個方法被保證會在提供給這個loader的數據被釋放之前調用。 這個時候,你應該移除所有舊數據的使用(因為它們馬上就會被釋放),但是不應該自己去釋放它們,因為它們的loader會做這些事。
一旦知道了應用將不會再使用這些數據,loader就應該立即釋放它們。 例如,數據是來自CursorLoader的一個cursor, 你就不應該再調用close()。如果這個cursor正要在 CursorAdapter中被替換,你應該使用swapCursor()方法使得舊的 Cursor不被關閉。例如:
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loaderloader, Cursor data) { //把新cursor換進來 (一旦我們返回了,框架將會管理 //關閉舊cursor的事情) mAdapter.swapCursor(data); }
onLoaderReset
當之前創建的loader被重置使得數據不可用的時候,此方法被調用。這個回調方法讓你弄清楚數據何時會被釋放,進而你可以移除對它的引用
下面的實現調用了參數為null的 swapCursor() :
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... public void onLoaderReset(Loaderloader) { //當最後一個Cursor進入到onLoadFinished()時被調用, //Cursor將要被關閉, 我們要確保 //不會再使用到它。 mAdapter.swapCursor(null); }
下面的例子完整實現了一個Fragment顯示一個包含了從聯系人content provider返回的查詢數據的ListView的內容的功能。 它使用一個CursorLoader來管理對provider的查詢。
一個應用想要實現操作用戶的聯系人,如例子中那樣,它的manifest一定要包含 READ_CONTACTS權限。
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks{ //這就是用來展示列表信息的Adapter。 SimpleCursorAdapter mAdapter; //如果不是null,這就是當前的搜索過濾器。 String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //如果沒有數據,就給控件一些文本去顯示。 //在真正的應用中,信息來自應用資源。 setEmptyText("No phone numbers"); //我們在action bar中顯示一個菜單項。 setHasOptionsMenu(true); //創建一個新的adapter,我們將用它來顯示加載的數據。 mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); //准備loader, 重連到一個已存在的loader, //或者啟動一個新的loader。 getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { //放置一個action bar用於搜索。 MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { //action bar上的搜索文本改變的時候被調用。 //更新搜索過濾器,並且重啟loader用當前的過濾器 //來做新的查詢。 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { //不必關心這個方法。 return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } //我們想獲取的聯系人中的行數據。 static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader onCreateLoader(int id, Bundle args) { //當需要創建一個新的loader時被調用。 //本例中僅有一個loader,所以我們不必關心ID的問題。 //首先,根據我們當前是否正在過濾, //選擇base URI來使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //現在創建並返回一個CursorLoader, //它將會為被顯示的數據創建一個Cursor。 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader loader, Cursor data) { //把新的cursor換進來。 (框架將會在我們返回的時候 //管理舊cursor的關閉事宜。) mAdapter.swapCursor(data); } public void onLoaderReset(Loader loader) { //當最後一個Cursor進入onLoadFinished()的時候被調用。 //cursor將要被關閉, 我們應該確保 //不再使用它。 mAdapter.swapCursor(null); } }
DrawerLayout組件同樣是V4包中的組件,也是直接繼承於ViewGroup類,所以這個類也是一個容器類。使用DrawerLayout可以輕松的實現抽屜效果,使用D
動畫(Animation) Android提供了2種動畫: Tween動畫 通過對 View 的內容進行一系列的圖形變換 (包括平移、縮放、旋轉、改變透明度)來實現動
隨著移動互聯網的快速發展,它已經和我們的生活息息相關了,在公交地鐵裡面都能看到很多人的人低頭看著自己的手機屏幕,從此“低頭族”一詞就產生了,作為一名移動行業的開發人員,我
一、為何本文不介紹Hook系統的AMS服務在之前一篇文章中已經講解了 Android中Hook系統服務,以及攔截具體方法的功能了,按照流程本文應該介紹如何Hook系統的A