前言
Loaders,裝載機,適用於Android3.0以及更高的版本,它提供了一套在UI的主線程中異步加載數據的框架。使用Loaders可以非常簡單的在Activity或者Fragment中異步加載數據,一般適用於大量的數據查詢,或者需要經常修改並及時展示的數據顯示到UI上,這樣可以避免查詢數據的時候,造成UI主線程的卡頓。
Loaders有以下特點:
- 可以適用於Activity和Fragment。
- 可以提供異步的方式加載數據。
- 監聽數據源,當數據改變的時候,將新的數據發布到UI上。
- Loaders使用Cursor加載數據,在更改Cursor的時候,會自動重新連接到最后配置的Cursor中讀取數據,因此不需要重新查詢數據。
在Android中使用Loaders機制,需要多個類和接口的配合,以下是它們大致的關系圖,之后的內容會對這幾個類或接口進行詳細講解:
LoaderManager
LoaderManager,裝載機管理器。用於在Activity或者Fragment中管理一個或多個Loader實例。在Activity或者Fragment中,可以通過getLoaderManager()方法獲取LoaderManager對象,它是一個單例模式。
介紹幾個LoaderManager提供的方法,用於管理Loader:
- Loader<D> initLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):初始化一個Loader,並注冊回調事件。
- Loader<D> restartLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):重新啟動或創建一個Loader,並注冊回調事件。
- Loader<D> getLoader(int id):返回給定Id的Loader,如果沒有找到則返回Null。
- void destroyLoader(int id):根據指定Id,停止和刪除Loader。
通過上面幾個方法的參數可以看到,都有一個id參數,這個Id是Loader的標識,因為LoaderManager可以管理一個或多個Loader,所以必須通過這個Id參數來唯一確定一個Loader。而InitLoader()、restartLoader()中的bundle參數,傳遞一個Bundle對象給LoaderCallbacks中的onCreateLoader()去獲取,下面介紹LoaderCallbacks。
LoaderManager.LoaderCallbacks
LoaderCallbacks是LoaderManager和Loader之間的回調接口。它是一個回調接口,所以我們需要實現其定義的三個方法:
- Loader<D> onCreateLoader(int id,Bundle bundle):根據指定Id,初始化一個新的Loader。
- void onLoadFinished(Loader<D> loader,D data):當Loader被加載完畢后被調用,在其中處理Loader獲取的Cursor數據。
- void onLoaderReset(Loader<D> loader):當Loader被銷毀的時候被調用,在其中可以使Loader的數據不可用。
從LoaderCallbacks的聲明的幾個方法中可以看到,它是一個泛型的接口,需要指定Loader數據的類型。如果是數據源是從一個ContentProvider中獲取的,一般直接使用它的子類CursorLoader,下面介紹CursorLoader。
Loader
Loader,一個抽象的類,用於執行異步加載數據,這個Loader對象可以監視數據源的改變和在內容改變后,以新數據的內容改變UI的展示。它是一個Loader的抽象接口,所有需要實現的Loader功能的類都需要實現這個接口,但是如果需要自己開發一個裝載機的話,一般並不推薦繼承Loader接口,而是繼承它的子類AsyncTaskLoader,這是一個以AsyncTask框架執行的異步加載。
Android中還提供了一個CursorLoader類,它是AsyncTaskLoader的子類,一個異步的加載數據的類,通過ContentResolver的標准查詢並返回一個Cursor。這個類實現了Loader的協議,以一種標准的方式查詢Cursor。
CursorLoader類有兩個構造函數,推薦使用第二個,因為使用第一個構造函數,需要還需要通過CursorLoader提供的一些了getXxx()方法設置對應的屬性:
- CursorLoader(Context context)
- CursorLoader(Context context,Uri uri,String[] projection,String selection ,String[] selectionArgs,String sortOrder)
SimpleCursorAdapter
在Android中,數據的展示都需要使用一個Adapter適配器,而Loader一般返回的就是一個Cursor的數據,可以使用BaseAdapter的一個子類SimpleCursorAdapter,它可以使用XML資源文件自定義一個布局在展示數據。它有兩個構造函數,但是有一個構造函數在API Level11之后就不推薦使用。下面是構造函數的簽名:
SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to,int flags).
最后一個參數flags是一個標識,標識當數據改變調用onContentChanged()的時候,是否通知ContentProvider數據的改變,如果無需監聽ContentProvider的改變,則可以傳0。對於SimpleCursorAdapter適配器的Cursor的改變,可以使用SimpleCursorAdapter.swapCursor(Cursor)方法,它會與舊的Cursor互換,並且返回舊的Cursor。
Demo
下面通過一個Demo來講解一下Loaders的使用。在這個Demo中,數據使用SQLite數據庫保存,而使用ContentProvider進行數據的請求與訪問。在SQLite數據庫中,存在一個Student表,它近有兩個字段:_id,name。在Demo中,使用一個ListView展示數據,使用LoaderManager管理一個Loader,並通過這個Loader的回調接口進行刷新ListView的數據顯示。進行對SQLite數據庫中的數據進行增加與刪除。下面不提供SQLiteOpenHelper和ContentProvider相關實現類的代碼,如有需要可以下載源碼查看,對於SQLite和ContentProvider的內容,不清楚的朋友可以參見博客:數據持久化之SQLite和ContentProvider。
實現代碼:
1 package com.example.loadermanagerdemo; 2 3 import android.net.Uri; 4 import android.os.Bundle; 5 import android.app.Activity; 6 import android.app.AlertDialog; 7 import android.app.LoaderManager; 8 import android.app.LoaderManager.LoaderCallbacks; 9 import android.content.ContentResolver; 10 import android.content.ContentValues; 11 import android.content.CursorLoader; 12 import android.content.Loader; 13 import android.database.Cursor; 14 import android.util.Log; 15 import android.view.ContextMenu; 16 import android.view.LayoutInflater; 17 import android.view.Menu; 18 import android.view.MenuInflater; 19 import android.view.MenuItem; 20 import android.view.View; 21 import android.view.ContextMenu.ContextMenuInfo; 22 import android.widget.AdapterView.AdapterContextMenuInfo; 23 import android.widget.Button; 24 import android.widget.EditText; 25 import android.widget.ListView; 26 import android.widget.SimpleCursorAdapter; 27 import android.widget.TextView; 28 29 public class MainActivity extends Activity { 30 private LoaderManager manager; 31 private ListView listview; 32 private AlertDialog alertDialog; 33 private SimpleCursorAdapter mAdapter; 34 private final String TAG="main"; 35 36 @Override 37 protected void onCreate(Bundle savedInstanceState) { 38 super.onCreate(savedInstanceState); 39 setContentView(R.layout.activity_main); 40 listview = (ListView) findViewById(R.id.listView1); 41 //使用一個SimpleCursorAdapter,布局使用android自帶的布局資源simple_list_item_1, android.R.id.text1 為simple_list_item_1中TextView的Id 42 mAdapter = new SimpleCursorAdapter(MainActivity.this, 43 android.R.layout.simple_list_item_1, null, 44 new String[] { "name" }, new int[] { android.R.id.text1 },0); 45 46 // 獲取Loader管理器。 47 manager = getLoaderManager(); 48 // 初始化並啟動一個Loader,設定標識為1000,並制定一個回調函數。 49 manager.initLoader(1000, null, callbacks); 50 51 // 為ListView注冊一個上下文菜單 52 registerForContextMenu(listview); 53 } 54 55 @Override 56 public void onCreateContextMenu(ContextMenu menu, View v, 57 ContextMenuInfo menuInfo) { 58 super.onCreateContextMenu(menu, v, menuInfo); 59 // 聲明一個上下文菜單,contentmenu中聲明了兩個菜單,添加和刪除 60 MenuInflater inflater = getMenuInflater(); 61 inflater.inflate(R.menu.contentmenu, menu); 62 } 63 64 @Override 65 public boolean onContextItemSelected(MenuItem item) { 66 67 switch (item.getItemId()) { 68 case R.id.menu_add: 69 // 聲明一個對話框 70 AlertDialog.Builder builder = new AlertDialog.Builder( 71 MainActivity.this); 72 // 加載一個自定義布局,add_name中有一個EditText和Button控件。 73 final View view = LayoutInflater.from(MainActivity.this).inflate( 74 R.layout.add_name, null); 75 Button btnAdd = (Button) view.findViewById(R.id.btnAdd); 76 btnAdd.setOnClickListener(new View.OnClickListener() { 77 78 @Override 79 public void onClick(View v) { 80 EditText etAdd = (EditText) view 81 .findViewById(R.id.username); 82 String name = etAdd.getText().toString(); 83 // 使用ContentResolver進行刪除操作,根據name字段。 84 ContentResolver contentResolver = getContentResolver(); 85 ContentValues contentValues = new ContentValues(); 86 contentValues.put("name", name); 87 Uri uri = Uri 88 .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student"); 89 Uri result = contentResolver.insert(uri, contentValues); 90 if (result != null) { 91 //result不為空證明刪除成功,重新啟動Loader,注意標識需要和之前init的標識一致。 92 manager.restartLoader(1000, null, callbacks); 93 } 94 // 關閉對話框 95 alertDialog.dismiss(); 96 97 Log.i(TAG, "添加數據成功,name="+name); 98 } 99 }); 100 builder.setView(view); 101 alertDialog = builder.show(); 102 return true; 103 case R.id.menu_delete: 104 // 獲取菜單選項的信息 105 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item 106 .getMenuInfo(); 107 // 獲取到選項的TextView控件,並得到選中項的那么 108 TextView tv = (TextView) info.targetView; 109 String name = tv.getText().toString(); 110 // 使用ContentResolver進行刪除操作 111 Uri url = Uri 112 .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student"); 113 ContentResolver contentResolver = getContentResolver(); 114 String where = "name=?"; 115 String[] selectionArgs = { name }; 116 int count = contentResolver.delete(url, where, selectionArgs); 117 if (count == 1) { 118 //這個操作僅刪除單挑記錄,如果刪除行為1 ,則重新啟動Loader 119 manager.restartLoader(1000, null, callbacks); 120 } 121 Log.i(TAG, "刪除數據成功,name="+name); 122 return true; 123 default: 124 return super.onContextItemSelected(item); 125 } 126 127 } 128 129 // Loader的回調接口 130 private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() { 131 132 @Override 133 public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { 134 // 在Loader創建的時候被調用,這里使用一個ContentProvider獲取數據,所以使用CursorLoader返回數據 135 Uri uri = Uri 136 .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student"); 137 CursorLoader loader = new CursorLoader(MainActivity.this, uri, 138 null, null, null, null); 139 Log.i(TAG, "onCreateLoader被執行。"); 140 return loader; 141 } 142 143 @Override 144 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 145 //刷新SimpleCursorAdapter的數據 146 mAdapter.swapCursor(cursor); 147 // 重新設定適配器 148 listview.setAdapter(mAdapter); 149 Log.i(TAG, "onLoadFinished被執行。"); 150 } 151 152 @Override 153 public void onLoaderReset(Loader<Cursor> loader) { 154 // 當Loader被從LoaderManager中移除的時候,被執行,清空SimpleCursorAdapter適配器的Cursor 155 mAdapter.swapCursor(null); 156 Log.i(TAG, "onLoaderReset被執行。"); 157 } 158 }; 159 160 @Override 161 public boolean onCreateOptionsMenu(Menu menu) { 162 getMenuInflater().inflate(R.menu.main, menu); 163 return true; 164 } 165 166 }
效果展示: