Android之數據存儲----使用LoaderManager異步加載數據庫



一、各種概念:

1、Loaders:

適用於Android3.0以及更高的版本,它提供了一套在UI的主線程中異步加載數據的框架。使用Loaders可以非常簡單的在Activity或者Fragment中異步加載數據,一般適用於大量的數據查詢,或者需要經常修改並及時展示的數據顯示到UI上,這樣可以避免查詢數據的時候,造成UI主線程的卡頓。

即使是查詢SQLite數據庫,用Loaders來操作會更加的簡便。

Loaders有以下特點:

  • 可以適用於Activity和Fragment。
  • 可以提供異步的方式加載數據。
  • 監聽數據源,當數據改變的時候,將新的數據發布到UI上。
  • Loaders使用Cursor加載數據,在更改Cursor的時候,會自動重新連接到最后配置的Cursor中讀取數據,因此不需要重新查詢數據。

在Android中使用Loaders機制,需要多個類和接口的配合,以下是它們大致的關系圖,之后的內容會對這幾個類或接口進行詳細講解:

2、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。

3、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。

4、CursorLoader

我們知道,Loader一個抽象的類,用於執行異步加載數據,這個Loader對象可以監視數據源的改變和在內容改變后,以新數據的內容改變UI的展示。它是一個抽象接口,所有需要實現的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)

 

二、代碼舉例:

在這個例子中,數據使用SQLite數據庫保存,然后用ContentProvider進行數據的請求與訪問。在SQLite數據庫中,已經存在一個Student表,它有兩個字段:_id,name。在本例中,使用一個ListView展示數據,使用LoaderManager管理一個Loader,並通過這個Loader的回調接口進行加載ListView的數據顯示並實時刷新,最終進行完成對SQLite數據庫中的數據進行增加與刪除。

整個工程文件的目錄結構如下:

具體步驟如下:

步驟(1):新建類PersonDao,用於進行對SQLite的CRUD操作

步驟(2):新建類DBHelper,用於初始化SQLiate數據庫

步驟(3):新建類PersonContentProvider,繼承ContetProvider,記得聲明權限。

步驟(4):添加單元測試類。我們在單元測試里向SQLite中添加一些記錄。

注:上述步驟是ContentProvider中的知識,和上一篇博文:ContentProvider內容提供者中的步驟一模一樣。所以這里就不列出代碼了,如果不明白的話,可以回去回顧一下,或者在本文的最后下載源碼也行。

我們在步驟(4)的單元測試里向數據庫中添加一些數據之后,可以開始接下來最關鍵的步驟了:

步驟(5):

MainActivity.java:

  1 package com.example.loadermanagertest;
  2 
  3 import android.annotation.SuppressLint;
  4 import android.app.Activity;
  5 import android.app.AlertDialog;
  6 import android.app.LoaderManager;
  7 import android.app.LoaderManager.LoaderCallbacks;
  8 import android.content.ContentResolver;
  9 import android.content.ContentValues;
 10 import android.content.CursorLoader;
 11 import android.content.Loader;
 12 import android.database.Cursor;
 13 import android.net.Uri;
 14 import android.os.Bundle;
 15 import android.util.Log;
 16 import android.view.ContextMenu;
 17 import android.view.ContextMenu.ContextMenuInfo;
 18 import android.view.LayoutInflater;
 19 import android.view.Menu;
 20 import android.view.MenuInflater;
 21 import android.view.MenuItem;
 22 import android.view.View;
 23 import android.widget.AdapterView.AdapterContextMenuInfo;
 24 import android.widget.Button;
 25 import android.widget.EditText;
 26 import android.widget.ListView;
 27 import android.widget.SimpleCursorAdapter;
 28 import android.widget.TextView;
 29 
 30 @SuppressLint("InflateParams") public class MainActivity extends Activity {
 31     private LoaderManager manager;
 32     private ListView listview;
 33     private AlertDialog alertDialog;
 34     private SimpleCursorAdapter mAdapter;
 35     private final String TAG="MainActivity";
 36 
 37     @Override
 38     protected void onCreate(Bundle savedInstanceState) {
 39         super.onCreate(savedInstanceState);
 40         setContentView(R.layout.activity_main);
 41         listview = (ListView) findViewById(R.id.listView1);
 42         //使用一個SimpleCursorAdapter,布局使用android自帶的布局資源simple_list_item_1, android.R.id.text1 為simple_list_item_1中TextView的Id
 43         mAdapter = new SimpleCursorAdapter(MainActivity.this,
 44                 android.R.layout.simple_list_item_1, null,
 45                 new String[] { "name" }, new int[] { android.R.id.text1 },0);
 46         
 47         // 獲取Loader管理器。
 48         manager = getLoaderManager();
 49         // 初始化並啟動一個Loader,設定標識為1000,並制定一個回調函數。
 50         manager.initLoader(1000, null, callbacks);
 51 
 52         // 為ListView注冊一個上下文菜單
 53         registerForContextMenu(listview);
 54     }
 55 
 56     @Override
 57     public void onCreateContextMenu(ContextMenu menu, View v,
 58             ContextMenuInfo menuInfo) {
 59         super.onCreateContextMenu(menu, v, menuInfo);
 60         // 聲明一個上下文菜單,contentmenu中聲明了兩個菜單,添加和刪除
 61         MenuInflater inflater = getMenuInflater();
 62         inflater.inflate(R.menu.contentmenu, menu);
 63     }
 64 
 65     //單擊單個的item,彈出菜單選項,讓你選擇是添加還是刪除
 66     @Override
 67     public boolean onContextItemSelected(MenuItem item) {
 68         
 69         switch (item.getItemId()) {
 70         //當用戶點擊菜單中的“添加”選項是,彈出對話框,在對話框里添加name的值
 71         case R.id.menu_add:
 72             // 添加一個對話框
 73             AlertDialog.Builder builder = new AlertDialog.Builder(
 74                     MainActivity.this);
 75             // 加載一個自定義布局,add_name中有一個EditText和Button控件。
 76             final View view = LayoutInflater.from(MainActivity.this).inflate(
 77                     R.layout.add_name, null);
 78             Button btnAdd = (Button) view.findViewById(R.id.btnAdd);
 79             btnAdd.setOnClickListener(new View.OnClickListener() {
 80 
 81                 @Override
 82                 public void onClick(View v) {
 83                     EditText etAdd = (EditText) view
 84                             .findViewById(R.id.username);
 85                     String name = etAdd.getText().toString();
 86                     // 使用ContentResolver進行刪除操作,根據name字段。
 87                     ContentResolver contentResolver = getContentResolver();
 88                     ContentValues contentValues = new ContentValues();
 89                     contentValues.put("name", name);
 90                     Uri uri = Uri
 91                             .parse("content://com.example.loadermanagertest.PersonContentProvider/person");
 92                     Uri result = contentResolver.insert(uri, contentValues);
 93                     if (result != null) {
 94                         //result不為空證明添加成功,重新啟動Loader,注意標識需要和之前init的標識一致。
 95                         manager.restartLoader(1000, null, callbacks);  96                     }
 97                     // 關閉對話框
 98                     alertDialog.dismiss();
 99                     
100                     Log.i(TAG, "--->>添加數據成功,name="+name);
101                 }
102             });
103             builder.setView(view);
104             alertDialog = builder.show();
105             return true;
106         case R.id.menu_delete:
107             // 獲取菜單選項的信息
108             AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
109                     .getMenuInfo();
110             // 獲取到選項的TextView控件,並得到選中項的內容
111             TextView tv = (TextView) info.targetView;
112             String name = tv.getText().toString();
113             // 使用ContentResolver進行刪除操作
114             Uri url = Uri
115                     .parse("content://com.example.loadermanagertest.PersonContentProvider/person");
116             ContentResolver contentResolver = getContentResolver();
117             String where = "name=?";
118             String[] selectionArgs = { name };
119             int count = contentResolver.delete(url, where, selectionArgs);
120             if (count == 1) {
121                 //這個操作僅刪除單條記錄,如果刪除行為1 ,則重新啟動Loader
122                 manager.restartLoader(1000, null, callbacks); 123             }
124             Log.i(TAG, "--->>刪除數據成功,name="+name);
125             return true;
126         default:
127             return super.onContextItemSelected(item);
128         }
129 
130     }
131 
132     // Loader的回調接口,在這里異步加載數據庫的內容,顯示在ListView上,同時能夠自動更新
133     private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() {
134 
135         @Override
136         public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
137             // 在Loader創建的時候被調用,這里使用一個ContentProvider獲取數據,所以使用CursorLoader返回數據
138             Uri uri = Uri
139                     .parse("content://com.example.loadermanagertest.PersonContentProvider/person");
140             CursorLoader loader = new CursorLoader(MainActivity.this, uri,
141                     null, null, null, null);
142             Log.i(TAG, "--->>onCreateLoader被執行。");
143             return loader;
144         }
145 
146         //完成對UI的數據提取,更新UI
147         @Override
148         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
149             //把數據提取出來,放到適配器中完成對UI的更新操作(刷新SimpleCursorAdapter的數據)
150             mAdapter.swapCursor(cursor);
151             // 為ListView綁定適配器
152             listview.setAdapter(mAdapter);
153             Log.i(TAG, "--->>onLoadFinished被執行。");
154         }
155 
156         @Override
157         public void onLoaderReset(Loader<Cursor> loader) {
158             // 當Loader被從LoaderManager中移除的時候,被執行,清空SimpleCursorAdapter適配器的Cursor
159             mAdapter.swapCursor(null);
160             Log.i(TAG, "--->>onLoaderReset被執行。");
161         }
162     };
163 
164     @Override
165     public boolean onCreateOptionsMenu(Menu menu) {
166         getMenuInflater().inflate(R.menu.main, menu);
167         return true;
168     }
169 
170 }

核心代碼:132行至162行、95行和122行的實時刷新

注意為ListView綁定適配器的代碼:listview.setAdapter(mAdapter)是在Loader的回調接口中(132行)進行的,也就是在這里更新UI,這樣就能夠實現自動刷新UI。

43行:新建一個SimpleCursorAdapter適配器

先通過getLoaderManager()方法獲取LoaderManager對象(48行),然后通過manager.initLoader(1000, null, callbacks)初始化一個Loader(50行)。其方法的完整版是:

public abstract <D> Loader<D> initLoader(int id,Bundle args, LoaderManager.LoaderCallbacks<D> callback)

  • 第一個參數id:一個Acticity中可以加載多個Loader,所以要給每個Loader制定一個唯一的標識符id。第二個參數可以置空。
  • 第三個參數callback:回調。

Loader的回調接口是在132行至162行定義的,也就是在這里異步加載數據庫的內容,顯示在ListView上,同時能夠自動更新

我們在第53行為ListView注冊一個上下文菜單,上下文的菜單布局是在62行的R.menu.contentmenu.xml中定義的(稍后給出代碼)。

當用戶單擊單個的item時,彈出菜單選項,讓你選擇是添加還是刪除(67行定義的方法)。如果是選擇添加內容,則彈出一個對話框(71行)(對話框的布局文件R.layout.add_name稍后給出),輸入需要加入的內容,單擊確定,就會更新到UI(95行的manager.restartLoader(1000, null, callbacks)方法);如果選擇刪除內容,則直接刪除,並更新UI(122行的manager.restartLoader(1000, null, callbacks)方法)。

R.menu.contentmenu.xml:用於定義上下文菜單的布局

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_add"
        android:orderInCategory="100"
        android:showAsAction="never"
        android:title="添加">
    </item>
    <item
        android:id="@+id/menu_delete"
        android:orderInCategory="100"
        android:showAsAction="never"
        android:title="刪除">
    </item>
</menu>

R.layout.add_name.xml:定義添加內容的布局

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="240dp"
 4     android:layout_height="wrap_content"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:id="@+id/textView1"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:text="姓名:" />
12 
13     <EditText
14         android:id="@+id/username"
15         android:layout_width="match_parent"
16         android:layout_height="wrap_content"
17         android:layout_marginBottom="4dp"
18         android:layout_marginLeft="4dp"
19         android:layout_marginRight="4dp"
20         android:layout_marginTop="16dp"
21         android:hint="username"
22         android:inputType="textEmailAddress" />
23 
24     <Button
25         android:id="@+id/btnAdd"
26         android:layout_width="match_parent"
27         android:layout_height="wrap_content" android:text="確定">
28     </Button>
29 
30 </LinearLayout>

布局效果如下:

8ead0ac2-df0c-4efd-9403-5da9f016959e

運行程序,動態演示效果如下:

 

圖文分解如下:

初始界面為:

71bab64b-aafe-445d-903d-8bf9c2feae7d

單擊長按第二個item,會彈出一個菜單:

0b7707d4-c959-44e6-8676-b4c45f25d342

如果我們選擇上圖菜單中的“添加”,會彈出一個對話框:

831b74ca-9828-450f-9ce9-fb75b07e16f9

我們在上面的對話框中輸入smyhvae,點擊“確定”,就會將內容添加到ListView中,並自動更新UI:

40974acd-1a9b-4ad6-a782-c23c477ec643

如果單擊菜單中的“刪除”,會直接刪除。

注:本文采用的適配器是SimpleCursorAdapter,可以參考老羅的老版視頻,采用的是自定義適配器BaseAdapter。

參考鏈接:http://www.cnblogs.com/plokmju/p/android_Loaders.html 

 

【工程文件】
密碼:rinl
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM