編輯:關於Android編程
一、Loader簡介: (一)、Loader的概念:
裝載器從android3.0開始引進。它使得在activity或fragment中異步加載數據變得簡單。
當成批顯示數據的時候,為了使用戶體驗更好,需要進行異步裝載。也就是說,讓未顯示數據的ListView等UI組件或控件先顯示,避免出現白屏的尴尬現象,同時在後台下載數據,等下載完成後再更新ListView組件。這樣盡管用戶不會立刻看到數據,但是也不至於網絡速度緩慢或服務器響應不及時而造成假死現象。
裝載器具有如下特性:
它們對每個Activity和Fragment都有效;
他們提供了異步加載數據的能力;
它擁有一個數據改變通知機制,當數據源做出改變時會及時通知。 也就是可以監聽數據源,一旦數據源發生變化,Loader會感知這些變化;
當Cursor 發生變化時,會自動加載數據,因此並不需要再重新進行數據查詢。
android設計Loader的初衷是想讓大家像CursorLoader的做法一樣,通過loader去維護數據,每次啟動loader時先檢查有沒有舊的數據並把舊的數據先deliver給用戶,然後再考慮要不要重新加載新的數據。
在使用裝載器時,會涉及很多類和接口們,在下表中對它們總結一下:
Class/Interface
說明
LoaderManager
一個抽像類,關聯到一個Activity或Fragment,管理一個或多個裝載器的實例。這幫助一個應用管理那些與Activity或Fragment的生命周期相關的長時間運行的的操作。最常見的方式是與一個CursorLoader一起使用,然而應用是可以隨便寫它們自己的裝載器以加載其它類型的數據。
每個activity或fragment只有一個LoaderManager。但是一個LoaderManager可以擁有多個裝載器。
LoaderManager.LoaderCallbacks
一個用於客戶端與LoaderManager交互的回調接口。例如,你使用回調方法onCreateLoader()來創建一個新的裝載器。
Loader(裝載器)
一個執行異步數據加載的抽象類。它是加載器的基類。你可以使用典型的CursorLoader,但是你也可以實現你自己的子類。一旦裝載器被激活,它們將監視它們的數據源並且在數據改變時發送新的結果。
AsyncTaskLoader
提供一個AsyncTask來執行異步加載工作的抽象類。
CursorLoader
AsyncTaskLoader的子類,它查詢ContentResolver然後返回一個Cursor。這個類為查詢cursor以標准的方式實現了裝載器的協議,它的游標查詢是通過AsyncTaskLoader在後台線程中執行,從而不會阻塞界面。使用這個裝載器是從一個ContentProvider異步加載數據的最好方式。相比之下,通過fragment或activity的API來執行一個被管理的查詢就不行了。
(三)、類目錄結構:
1、API11中開始加入Loader:
java.lang.Object
? android.content.Loader
? android.content.AsyncTaskLoader
子類:
java.lang.Object
? android.content.Loader
? android.content.AsyncTaskLoader
? android.content.CursorLoader
2、為了兼容1.6以下版本:
java.lang.Object
? android.support.v4.content.Loader
? android.support.v4.content.AsyncTaskLoader
子類:
java.lang.Object
? android.support.v4.content.Loader
? android.support.v4.content.AsyncTaskLoader
? android.support.v4.content.CursorLoader
二、AsyncTaskLoader示例:
(一)、AsyncTaskLoader實現數據加載的步驟:
1、窗體Activity要實現LoaderManager.LoaderCallbacks
2、創建LoaderManager對象:通過getLoaderManager()或getSupportLoaderManager()方法來實現。如果是繼承於FragmentActivity類,則使用getSupportLoaderManager()方法來創建LoaderManger對象,否則使用前者創建即可;
3、初始化LoaderManager對象:調用initLoader()方法來初始化;
initLoader()方法有以下參數:一個唯一ID來標志裝載器5、自定義Loader,作為onCreateLoader()的返回值(也就是說onCreateLoader()方法必須返回自定義Loader的實例);
自定義Loader要繼承於AsyncTaskLoader
FLAG_AUTO_REQUERYandFLAG_REGISTER_CONTENT_OBSERVER。
FLAG_AUTO_REQUERY(常量值:1 )從 API11 開始已經廢棄。因為他會在應用程序的 UI 線程中執行游標查詢操作, 導致響應緩慢甚至應用程序無響應(ANR)的錯誤。作為替代方案,請使用LoaderManager和AsyncTaskLoader、CursorLoader。
如果設置FLAG_REGISTER_CONTENT_OBSERVER(常量值:2),適配器會在Cursor上注冊一個內容觀測器,當通知到達時會調用onContentChanged()方法。
(二)、LoaderManager.LoaderCallbacks主要回調方法:
1、onCreateLoader() :初始化並返回一個新的Loader;
當你試圖去操作一個裝載器時(比如,通過initLoader()),會檢查是否指定ID的裝載器已經存在.如果它不存在,將會觸發LoaderManager.LoaderCallbacks 的方法onCreateLoader();
2、onLoadFinished():當一個裝載器完成了它的裝載過程後被調用;
這個方法是在前面已創建的裝載器已經完成其加載過程後被調用.這個方法保證會在應用到裝載器上的數據被釋放之前被調用;
3、onLoaderReset() :當一個裝載器被重置而其數據無效時被調用。
所謂Loader的重置,就是指Loader對象還保留,只是清除Loader中的數據,所以onLoaderReset()方法相當於Loader的銷毀方法。因此在onLoaderReset()方法中會找到即將釋放的數據的引用,並移除這些引用。移除引用後,GC才可以清除這些數據。
當一個已創建的裝載器被重置從而使其數據無效時,此方法被調用.此回調使你能發現什麼時候數據將被釋放。你可以釋放對它的引用。
(四)、AsyncTaskLoader中各個方法的執行順序:
04-01 04:00:25.477: MainActivity: ==onCreate
04-01 04:00:25.701: LoaderCallbacks: ==onCreateLoader
04-01 04:00:25.705: AsyncTaskLoader: ==onStartLoading
04-01 04:00:25.709: AsyncTaskLoader: ==loadInBackground
04-01 04:00:25.721: AsyncTaskLoader: ==deliverResult
04-01 04:00:25.721: LoaderCallbacks: ==onLoadFinished
04-01 04:00:25.749: MainActivity: ==onStart
04-01 04:00:25.749: MainActivity: ==onResume
04-01 04:00:25.973: MainActivity: ==onCreateOptionsMenu
(五)、當另一個app修改了同一個數據源(如:共同使用的SDCard上的數據庫)後AsyncTaskLoader中各個方法的執行順序:
04-01 04:01:06.693: MainActivity: ==onPause
04-01 04:01:08.413: MainActivity: ==onStop
04-01 04:01:15.721: AsyncTaskLoader: ==onStartLoading
04-01 04:01:15.721: AsyncTaskLoader: ==loadInBackground
04-01 04:01:15.721: AsyncTaskLoader: ==deliverResult
04-01 04:01:15.721: LoaderCallbacks: ==onLoadFinished
04-01 04:01:15.757: MainActivity: ==onStart
04-01 04:01:15.757: MainActivity: ==onResume
(三)、示例代碼:
publicclass MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks { private ListView listView_main; private LoaderManager manager; privatestatic SimpleCursorAdapter adapter; @Override protectedvoid onCreate(Bundle arg0) { super.onCreate(arg0); setContentView(R.layout.activity_main); listView_main = (ListView) findViewById(R.id.listView_main); manager = getSupportLoaderManager(); manager.initLoader(0, null, this); adapter = new SimpleCursorAdapter(this, R.layout.item_listview_main, null, new String[] { "_id", "title" }, newint[] { R.id.text_item_title, R.id.text_item_content }, 2); listView_main.setAdapter(adapter); } @Override public Loader onCreateLoader(int arg0, Bundle arg1) { Log.i("Activity", "===onCreateLoader"); returnnew MyLoader(this); } @Override publicvoid onLoadFinished(Loader arg0, Cursor arg1) { Log.i("Activity", "===onLoaderFinished"); adapter.swapCursor(arg1); } @Override publicvoid onLoaderReset(Loader arg0) { Log.i("Activity", "===onLoaderReset"); } staticclass MyLoader extends AsyncTaskLoader { private Context context; public MyLoader(Context context) { super(context); this.context = context; } @Override protectedvoid onStartLoading() { super.onStartLoading(); Log.i("MyLoader", "===onStartLoading"); forceLoad(); } @Override public Cursor loadInBackground() { Log.i("MyLoader", "===loadInBackground"); MySQLiteDatabaseHelper dbHelper = new MySQLiteDatabaseHelper(); Cursor cursor = dbHelper.selectCursor( "select id as _id , title from android_info limit 0,20", null); deliverResult(cursor); return cursor; } @Override publicvoid deliverResult(Cursor data) { Log.i("MyLoader", "===deliverResult"); super.deliverResult(data); adapter.swapCursor(data); } } }
三、CursorLoader實例:
(一)、CursorLoader實現數據加載的步驟:
1、窗體Activity要實現LoaderManager.LoaderCallbacks
2、創建LoaderManager對象:通過getLoaderManager()或getSupportLoaderManager()方法來實現。如果是繼承於FragmentActivity類,則使用getSupportLoaderManager()方法來創建LoaderManger對象,否則使用前者創建即可;
3、初始化LoaderManager對象:調用initLoader()方法來初始化;
initLoader()方法有以下參數:
一個唯一ID來標志裝載器
可選的參數,用於裝載器初始化時
一個LoaderManager.LoaderCallbacks的實現。被LoaderManager調用以報告裝載器的事件。一般窗體都實現了這個接口,所以傳的是它自己:this;
initLoader()保證一個裝載器被初始化並激活.它具有兩種可能的結果:
如果ID所指的裝載器已經存在,那麼這個裝載器將被重用;
如果裝載器不存在,initLoader()就觸發LoaderManager.LoaderCallbacks中的回調方法onCreateLoader()。這是實例化並返回一個新Loader的地方。 4、操作ListView控件對象:先findViewById(),然後setAdapter();
【備注:】此時必須使用SimpleCursorAdater適配器,而構建適配器的時候,第三個參數Cursor設置為null,最後一個參數:flags必須是:CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER。
5、重寫onCreateLoader()方法。返回CursorLoader的構造方法。
例如: return new CursorLoader(this, uri, null, null, null, null);
【備注:】new CursorLoader()的參數:
1、 uri —要獲取的內容的URI;
2、projection —要返回的列組成的數組。傳入null 將會返回所有的列,但這樣會導致低效;
3、selection —表明哪些行將被返回,相當於SQL語句中的WHERE條件 (不包括WHERE關鍵詞)。傳入null 將返回所有的行;
4、selectionArgs —Where語句中的'?’組成的數組。
5、sortOrder —如何排序,相當於SQL語句中的 ORDER BY 語句(不包括ORDER BY關鍵詞)。傳入null將使用默認順序。
(二)、示例代碼:
publicclass MainActivity extends Activity implements LoaderCallbacks { private ListView listView_main; private Uri uri = Uri .parse("content://com.steven.mywordsprovider/tb_words"); private SimpleCursorAdapter adapter = null; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("MainActivity", "==onCreate"); listView_main = (ListView) findViewById(R.id.listView_main); adapter = new SimpleCursorAdapter(this, R.layout.item_listview, null, new String[] { "words", "detail" }, newint[] { R.id.text_item_listview_id, R.id.text_item_listview_title }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); listView_main.setAdapter(adapter); LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(2, null, this); } @Override public Loader onCreateLoader(int id, Bundle args) { Log.i("Callbacks", "==onCreateLoader"); returnnew CursorLoader(this, uri, null, null, null, null); } @Override publicvoid onLoadFinished(Loader loader, Cursor data) { Log.i("Callbacks", "==onLoadFinished"); adapter.swapCursor(data); } @Override publicvoid onLoaderReset(Loader loader) { Log.i("Callbacks", "==onLoaderReset"); adapter.swapCursor(null); } @Override publicboolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); Log.i("MainActivity", "==onCreateOptionsMenu"); returntrue; } }
四、CursorLoader結合ActionBar上的SearchView實現過濾搜索:
(一)、主窗體布局核心代碼:
(二)、Menu菜單的xml:
(三)、主窗體Java核心代碼:
publicclass MainActivity extends Activity implements LoaderCallbacks { private ListView listView_main; private TextView text_emptyinfo; private SimpleCursorAdapter adapter = null; private String uri_path; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("MainActivity", "==onCreate"); listView_main = (ListView) findViewById(R.id.listView_main); text_emptyinfo = (TextView) findViewById(R.id.text_emptyinfo); adapter = new SimpleCursorAdapter(this, R.layout.item_listview, null, new String[] { "words", "detail" }, newint[] { R.id.text_item_listview_id, R.id.text_item_listview_title }, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); listView_main.setAdapter(adapter); listView_main.setEmptyView(text_emptyinfo); LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(2, null, this); } @Override public Loader onCreateLoader(int id, Bundle args) { Log.i("Callbacks", "==onCreateLoader"); Uri baseUri = Uri .parse("content://com.steven.mywordsprovider/tb_words"); if (uri_path != null) { baseUri = Uri.withAppendedPath(baseUri, uri_path); } returnnew CursorLoader(this, baseUri, null, null, null, null); } @Override publicvoid onLoadFinished(Loader loader, Cursor data) { Log.i("Callbacks", "==onLoadFinished"); adapter.swapCursor(data); } @Override publicvoid onLoaderReset(Loader loader) { Log.i("Callbacks", "==onLoaderReset"); adapter.swapCursor(null); } @Override publicboolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); SearchView searchView_actionBar = (SearchView) menu.findItem( R.id.searchView_actionBar).getActionView(); searchView_actionBar.setOnQueryTextListener(new OnQueryTextListener() { @Override publicboolean onQueryTextSubmit(String query) { returnfalse; } @Override publicboolean onQueryTextChange(String newText) { if (newText == null) { uri_path = null; } else { uri_path = newText; } // 更新搜索過濾,重新啟動Loader進行新的查詢. getLoaderManager().restartLoader(0, null, MainActivity.this); returntrue; } }); returntrue; } }
(四)、ContentProvider中的核心代碼:
publicclass MyProvider extends ContentProvider { private MySQLiteOpenHelper dbHelper = null; private SQLiteDatabase db = null; privatestatic UriMatcher matcher = null; privatestatic String AUTHORITY = "com.steven.mywordsprovider"; static { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(AUTHORITY, "tb_words", 1); matcher.addURI(AUTHORITY, "tb_words/*", 2); matcher.addURI(AUTHORITY, "tb_words/#", 3); } @Override publicboolean onCreate() { dbHelper = new MySQLiteOpenHelper(getContext()); db = dbHelper.getReadableDatabase(); returntrue; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (matcher.match(uri)) { case 1: Cursor cursor = db.query("tb_words", projection, selection, selectionArgs, null, null, sortOrder); return cursor; case 2: String search_str = uri.getPathSegments().get(1); if (selection == null) { selection = "words like '" + search_str + "%'"; } else { selection += " and words like '" + search_str + "'%"; } Cursor cursor2 = db.query("tb_words", projection, selection, selectionArgs, null, null, sortOrder); return cursor2; default: break; } returnnull; } @Override public String getType(Uri uri) { returnnull; } @Override public Uri insert(Uri uri, ContentValues values) { returnnull; } @Override publicint delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override publicint update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
前面一篇文章,分析了AppWidgetProvider和RemoteView的源碼,從中我們可以知道它們的實現原理,AppWidgetProvider是一個Broadca
簡介 RakNet在Win平台上已經實現消息、語音、文件傳輸了,但在Android平台下尚未實現,筆者決定把源碼移植到Android平台下測試。詳情實現消息項
這一章很多,但是很有趣,也是這書的最後一章知識點了,我現在還在考慮要不要寫這個拼圖和2048的案例,在此之前,我們先來玩玩Android5.X的新特性吧!Android
概述在設計應用的時候,我們應該熱愛極簡主義,簡單就是好的,對於很多用戶來說,復雜的東西並不受歡迎。我要實現的是根據不同的情況去顯示不同的加載效果,隨用隨調,效果是借鑒於某