Android Loader(二) CursorLoader

来源:互联网 发布:gta5怎么修改车辆数据 编辑:程序博客网 时间:2024/05/21 10:34

Android Loader(一) 概述

Android Loader(三) 结合CursorLoader分析Loader相关源码

CursorLoader是AsyncTaskLoader的子类,它可以异步查询ContentProvider中得数据,在ContentProvider中数据变化时,自动重新查询。

通过CursorLoader加载ContentProvider中数据有以下2步:

1. 实现LoaderManager.LoaderCallbacks<Cursor>接口。

2. 重写onCreateLoader,onLoadFinished,onLoaderReset方法。

下面用一个API DEMO例子说明:

以下代码实现一个模糊查询手机中联系人,并显示在listview上的功能,由于联系人的数据系统已经通过Contacts的ContentProvider提供,访问联系人数据直接调用系统URI即可。

public class LoaderCursor extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        FragmentManager fm = getFragmentManager();        if (fm.findFragmentById(android.R.id.content) == null) {            CursorLoaderListFragment list = new CursorLoaderListFragment();            fm.beginTransaction().add(android.R.id.content, list).commit();        }    }    public static class CursorLoaderListFragment extends ListFragment            implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {        SimpleCursorAdapter mAdapter;        // If non-null, this is the current filter the user has provided.        String mCurFilter;        @Override public void onActivityCreated(Bundle savedInstanceState) {            super.onActivityCreated(savedInstanceState);            // Give some text to display if there is no data.  In a real            // application this would come from a resource.            setEmptyText("No phone numbers");            // We have a menu item to show in action bar.            setHasOptionsMenu(true);            // Create an empty adapter we will use to display the loaded data.            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);            // Start out with a progress indicator.            setListShown(false);            // Prepare the loader.  Either re-connect with an existing one,            // or start a new one.            getLoaderManager().initLoader(0, null, this);        }        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {            // Place an action bar item for searching.            MenuItem item = menu.add("Search");            item.setIcon(android.R.drawable.ic_menu_search);            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM                    | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);            SearchView sv = new SearchView(getActivity());            sv.setOnQueryTextListener(this);            item.setActionView(sv);        }        public boolean onQueryTextChange(String newText) {            // Called when the action bar search text has changed.  Update            // the search filter, and restart the loader to do a new query            // with this filter.            String newFilter = !TextUtils.isEmpty(newText) ? newText : null;            // Don't do anything if the filter hasn't actually changed.            // Prevents restarting the loader when restoring state.            if (mCurFilter == null && newFilter == null) {                return true;            }            if (mCurFilter != null && mCurFilter.equals(newFilter)) {                return true;            }            mCurFilter = newFilter;            getLoaderManager().restartLoader(0, null, this);            return true;        }        @Override public boolean onQueryTextSubmit(String query) {            // Don't care about this.            return true;        }        @Override public void onListItemClick(ListView l, View v, int position, long id) {            // Insert desired behavior here.            Log.i("FragmentComplexList", "Item clicked: " + id);        }        // These are the Contacts rows that we will retrieve.        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<Cursor> onCreateLoader(int id, Bundle args) {            // This is called when a new Loader needs to be created.  This            // sample only has one Loader, so we don't care about the ID.            // First, pick the base URI to use depending on whether we are            // currently filtering.            Uri baseUri;            if (mCurFilter != null) {                baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,                        Uri.encode(mCurFilter));            } else {                baseUri = Contacts.CONTENT_URI;            }            // Now create and return a CursorLoader that will take care of            // creating a Cursor for the data being displayed.            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<Cursor> loader, Cursor data) {            // Swap the new cursor in.  (The framework will take care of closing the            // old cursor once we return.)            mAdapter.swapCursor(data);            // The list should now be shown.            if (isResumed()) {                setListShown(true);            } else {                setListShownNoAnimation(true);            }        }        public void onLoaderReset(Loader<Cursor> loader) {            // This is called when the last Cursor provided to onLoadFinished()            // above is about to be closed.  We need to make sure we are no            // longer using it.            mAdapter.swapCursor(null);        }    }}
在onActivityCreated方法中调用getLoaderManager().initLoader(0, null, this);初始化Loader,在 Fragment中实现了LoaderManager.LoaderCallbacks<Cursor>接口, 

onCreateLoader以ContentProvider的URI为参数,创建并返回一个CursorLoader。 在查询完毕后,会调用 onLoadFinished回调,所以在调用SimpleCursorAdapter的swapCursor(data),将查询后的数据显示在ListView上。最后,在Loader的数据不再使用时,onLoaderReset被调用, mAdapter.swapCursor(null); 保证旧的Cursor数据不再被使用。在 onQueryTextChange方法中,调用  getLoaderManager().restartLoader(0, null, this); 会使Loader重新创建, onCreateLoader-》onLoadFinished的回调方法会被重新执行。 


下面从ContentProvider实现开始,实现一个CursorLoader的完整例子:

下面的程序实现的功能:从记录城市的表CITY表中查询出城市名称,显示在ListView上。

工程目录:


先定义表结构:

CityTable.java:

public class CityTable {public static final String TABLE_NAME = "city";public static final String _ID = "_id";public static final String NAME = "name";public static final Uri CONTENT_URI =  Uri.parse("content://" + CityProvider.AUTHORITY + "/" + TABLE_NAME);public static final Uri CONTENT_BASE_URI =  Uri.parse("content://" + CityProvider.AUTHORITY + "/" + TABLE_NAME + "/");}

再实现DataBaseHelper

DataBaseHelper.java:

public class DataBaseHelper extends SQLiteOpenHelper {private static final String DATABASE_NAME = "city.db";    private static final int DATABASE_VERSION = 1;public DataBaseHelper(Context context, int version) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + CityTable.TABLE_NAME + " ("                 + CityTable._ID + " INTEGER PRIMARY KEY,"                 + CityTable.NAME + " TEXT"                 + ");");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS notes");        onCreate(db);}}
再实现提供CityTale数据的ContentProvider:

CityProvider.java:

public class CityProvider extends ContentProvider {public static final String AUTHORITY = "com.example.cityloader.provider";private static final int MAIN = 1;private static final int MAIN_ID = 2;private DataBaseHelper mDataBaseHelper;private final UriMatcher mUriMatcher;public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.example.cityloader.citytable";public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.example.cityloader.citytable";public CityProvider() {mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);mUriMatcher.addURI(AUTHORITY, CityTable.TABLE_NAME, MAIN);mUriMatcher.addURI(AUTHORITY, CityTable.TABLE_NAME + "/#", MAIN_ID);}@Overridepublic boolean onCreate() {mDataBaseHelper = new DataBaseHelper(getContext());return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {switch (mUriMatcher.match(uri)) {case MAIN:break;case MAIN_ID:selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,new String[] { uri.getLastPathSegment() });break;default:throw new IllegalArgumentException("Unknown URI " + uri);}SQLiteDatabase db = mDataBaseHelper.getReadableDatabase();Cursor c = db.query(CityTable.TABLE_NAME, null, selection,selectionArgs, null, null, null);c.setNotificationUri(getContext().getContentResolver(), uri);return c;}@Overridepublic String getType(Uri uri) {switch (mUriMatcher.match(uri)) {case MAIN:return CONTENT_TYPE;case MAIN_ID:return CONTENT_ITEM_TYPE;default:throw new IllegalArgumentException("Unknown URI " + uri);}}@Overridepublic Uri insert(Uri uri, ContentValues values) {if (mUriMatcher.match(uri) != MAIN) {throw new IllegalArgumentException("Unknown URI " + uri);}SQLiteDatabase db = mDataBaseHelper.getWritableDatabase();long rowId = db.insert(CityTable.TABLE_NAME, null, values);if (rowId > 0) {Uri noteUri = ContentUris.withAppendedId(CityTable.CONTENT_BASE_URI, rowId);getContext().getContentResolver().notifyChange(noteUri, null);return noteUri;}throw new SQLException("Failed to insert row into " + uri);}@Overridepublic int delete(Uri uri, String whereClause, String[] whereArgs) {SQLiteDatabase db = mDataBaseHelper.getWritableDatabase();String finalWhere;int count = 0;switch (mUriMatcher.match(uri)) {case MAIN:count = db.delete(CityTable.TABLE_NAME, whereClause, whereArgs);break;case MAIN_ID:finalWhere = DatabaseUtils.concatenateWhere(CityTable._ID + " = "+ ContentUris.parseId(uri), whereClause);count = db.delete(CityTable.TABLE_NAME, finalWhere, whereArgs);break;default:throw new SQLException("Failed to insert row into " + uri);}getContext().getContentResolver().notifyChange(uri, null);return count;}@Overridepublic int update(Uri uri, ContentValues values, String whereClause,String[] whereArgs) {SQLiteDatabase db = mDataBaseHelper.getWritableDatabase();int count = 0;String finalWhere;switch (mUriMatcher.match(uri)) {case MAIN:count = db.update(CityTable.TABLE_NAME, values, whereClause,whereArgs);break;case MAIN_ID:finalWhere = DatabaseUtils.concatenateWhere(CityTable._ID + " = "+ ContentUris.parseId(uri), whereClause);count = db.update(CityTable.TABLE_NAME, values, finalWhere,whereArgs);break;default:throw new SQLException("Failed to insert row into " + uri);}getContext().getContentResolver().notifyChange(uri, null);return count;}}

query方法中的 c.setNotificationUri(getContext().getContentResolver(), uri);注册了URi内容变化通知,Cursor会监听Uri内容的变化,当内容变化时,Cursor会自动更新数据。

在insert,delete,update方法的最后,调用getContext().getContentResolver().notifyChange(uri, null);来通知内容的改变。

接着,fragment实现  LoaderManager.LoaderCallbacks<Cursor> ,并重写回调。

@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {CursorLoader cl = new CursorLoader(getActivity(),CityTable.CONTENT_URI, null, null, null, null);cl.setUpdateThrottle(1000); // update at most every 2 seconds.return cl;}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {mAdapter.swapCursor(data);if (isResumed()) {setListShown(true);} else {setListShownNoAnimation(true);}}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {mAdapter.swapCursor(null);}

cl.setUpdateThrottle(1000);指定了,数据发生变化时,过1秒再刷新内容。

接下来,在菜单中添加插入/删除数据的操作:

@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {        menu.add(Menu.NONE, POPULATE_ID, 0, "Populate")                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);        menu.add(Menu.NONE, CLEAR_ID, 0, "Clear")                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);    } @Override public boolean onOptionsItemSelected(MenuItem item) {         final ContentResolver cr = getActivity().getContentResolver();         switch (item.getItemId()) {             case POPULATE_ID:                 if (mPopulatingTask != null) {                     mPopulatingTask.cancel(false);                 }                 mPopulatingTask = new AsyncTask<Void, Void, Void>() {                     @Override protected Void doInBackground(Void... params) {                         for (int i = 0; i < cities.length; i++) {                             if (isCancelled()) {                                 break;                             }                                                          ContentValues values = new ContentValues();                             values.put(CityTable.NAME, cities[i]);                             cr.insert(CityTable.CONTENT_URI, values);                                                          try {                                 Thread.sleep(250);                             } catch (InterruptedException e) {                             }                         }                         return null;                     }                 };                 mPopulatingTask.executeOnExecutor(                         AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);                 return true;             case CLEAR_ID:                 if (mPopulatingTask != null) {                     mPopulatingTask.cancel(false);                     mPopulatingTask = null;                 }                 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {                     @Override protected Void doInBackground(Void... params) {                         cr.delete(CityTable.CONTENT_URI, null, null);                         return null;                     }                 };                 task.execute((Void[])null);                 return true;             default:                 return super.onOptionsItemSelected(item);         }     }
之后,在manifest文件中增加ContentProvider的声明

        <provider             android:name="com.example.cityloader.CityProvider"            android:authorities="com.example.cityloader.provider"            ></provider>
最后,在Activity中显示Fragment

MainActivity.java:

public class MainActivity extends ActionBarActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);FragmentManager fm = getFragmentManager();if (fm.findFragmentById(android.R.id.content) == null) {CityListFragment list = new CityListFragment();fm.beginTransaction().add(android.R.id.content, list).commit();}}}
CityListFragment.java完整代码:

public class CityListFragment extends ListFragment implementsLoaderManager.LoaderCallbacks<Cursor> {static final int POPULATE_ID = Menu.FIRST;static final int CLEAR_ID = Menu.FIRST + 1;SimpleCursorAdapter mAdapter;AsyncTask<Void, Void, Void> mPopulatingTask;String[] cities = { "北京", "上海", "广州", "深圳" };@Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {menu.add(Menu.NONE, POPULATE_ID, 0, "Populate").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);menu.add(Menu.NONE, CLEAR_ID, 0, "Clear").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {final ContentResolver cr = getActivity().getContentResolver();switch (item.getItemId()) {case POPULATE_ID:if (mPopulatingTask != null) {mPopulatingTask.cancel(false);}mPopulatingTask = new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... params) {for (int i = 0; i < cities.length; i++) {if (isCancelled()) {break;}ContentValues values = new ContentValues();values.put(CityTable.NAME, cities[i]);cr.insert(CityTable.CONTENT_URI, values);try {Thread.sleep(250);} catch (InterruptedException e) {}}return null;}};mPopulatingTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,(Void[]) null);return true;case CLEAR_ID:if (mPopulatingTask != null) {mPopulatingTask.cancel(false);mPopulatingTask = null;}AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {@Overrideprotected Void doInBackground(Void... params) {cr.delete(CityTable.CONTENT_URI, null, null);return null;}};task.execute((Void[]) null);return true;default:return super.onOptionsItemSelected(item);}}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);setEmptyText("No data.");setHasOptionsMenu(true);mAdapter = new SimpleCursorAdapter(getActivity(),android.R.layout.simple_list_item_1, null,new String[] { CityTable.NAME },new int[] { android.R.id.text1 }, 0);setListAdapter(mAdapter);setListShown(false);getLoaderManager().initLoader(0, null, this);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {CursorLoader cl = new CursorLoader(getActivity(),CityTable.CONTENT_URI, null, null, null, null);cl.setUpdateThrottle(1000); // update at most every 2 seconds.return cl;}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {mAdapter.swapCursor(data);if (isResumed()) {setListShown(true);} else {setListShownNoAnimation(true);}}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {mAdapter.swapCursor(null);}}

效果:


0 0
原创粉丝点击