原文链接:http://www.sxrczx.com/pages/kohoh1992.github.io/cursor-auto-sync/index_1431878338570.html

在Android日常开发中,时常会请求数据到Cursor,然后再通过Cursor获取数据。像SQLiteDatabase和ContentProvider都使用了Cursor。在这些应用中,往往希望当数据发生改变时,Cursor也会自动的更新数据。这篇文章,我就会向你阐述如何通过Android自身的API实现Cursor的自动更新。另外我还将向你阐述这背后的原理。通过这些原理你可以举一反三的实现更为广泛的自动跟新。

文章中的代码

可以在https://github.com/KOHOH1992/CursorSyncDemo中找到文章中出现的代码

该项目共有4个分支。use_provider分支介绍了使用ContentProvider实现Cursor同步更新的方法。use_database分支介绍了不使用ContentProvider实现Cursor同步更新的方法。use_adapter分支介绍了不使用Loader实现Cursor同步更新的方法。

Cursor自动更新的实现

前提

首先假设项目使用了如下的前提

  • 数据存储在SqliteDataBase当中
  • 通对ContentProvider的请求,获取封装了数据的Cursor
  • 使用CursorLoader加载数据
  • 使用AdapterView和CursorAdapter显示数据

定义同步标志

static final Uri SYNC_SIGNAL_URI = Uri.parse("content://com.kohoh.cursorsyncdemo/SYNC_SIGNAL");

在ContentProvider的query中设置NotificationUri

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs,String sortOrder) {SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(ContactContract.CONTACT_TABLE, projection,selection,selectionArgs, null, null, sortOrder);//设置NotificationUricursor.setNotificationUri(contentResolver, ContactContract.SYNC_SIGNAL_URI);return cursor;
}

在ContentProvider的insert,update,delete中触发NotificationUri

@Override
public Uri insert(Uri uri, ContentValues values) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();long id = database.insert(ContactContract.CONTACT_TABLE, null, values);if (id >= 0) {//触发NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return uri.withAppendedPath(ContactContract.CONTACT_URI, String.valueOf(id));
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.update(ContactContract.CONTACT_TABLE, values, selection, selectionArgs);if (result > 0) {//触发NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.delete(ContactContract.CONTACT_TABLE, selection, selectionArgs);if (result > 0) {//触发NotificationUricontentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;
}

CursorLoader

ForceLoadContentObserver mObserver;public Cursor loadInBackground() {...try {//不过多解释,耗时的查询操作Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,mSelectionArgs, mSortOrder, mCancellationSignal);if (cursor != null) {try {// Ensure the cursor window is filled.cursor.getCount();//给Cursor设置观察者;ContentProvider通知Cursor的观察者数据发生了改变,//Cursor通知CursorLoader的观察者数据发生了改变,CursorLoader通过ContentProvider重新加载新的数据cursor.registerContentObserver(mObserver);//cursor.setNotificationUri(getContext().getContentResolver(), otificationUri);//给Cursor设置要观察的URI} catch (RuntimeException ex) {cursor.close();throw ex;}}return cursor;} }

Cursor的实现原理

Android的Cursor自动更新是通过观察者模式实现的,整个过程如下图所示

  • 通过ContentPorvider和ContentResolver使得数据发生了改变
  • ContentProvider通知Cursor的观察者数据发生了改变
  • Cursor通知CursorLoader的观察者数据发生了改变
  • CursorLoader通过ContentProvider加载新的数据
  • ContentPovider向DataBase请求新的数据
  • CursorLoader调用CursorAdapter# changeCursor,用封装了新数据的Cursor替换旧的Cursor
  • CursorAdapter告知AdapterView的观察者有新的数据
  • AdapterView重新加载并显示数据

在Android的android.database包下,有一个ContentObserver。Android正是通过他来实现观察者模式的。当数据改变之后,观察者会将数据改变的消息通知相应的对象,进而做出反馈。在代码中,当数据改变之后,我会调用ContentResolver# notifyChange,发出ContactContract.SYNC_SIGNAL_URI信号,通知数据发生了改变。而在此之前,从ContentProvider# query中获得的Cursor已经通过Cursor# setNotificationUri对ContactContract.SYNC_SIGNAL_URI信号进行了监视。当该信号出现,Cursor就会将信息改变的消息告诉CursorLoader的观察者(在此之前CursorLoader已经对该Cursor设立了观察者)。CursorLoader会开始重新开始加载数据。当数据加载成功,CursorLoader会通过CursorAdapter# changeCursor设置封装了新数据的Cursor。而后CursorAdapter又会通知AdapterView的观察者数据发生了改变(在此之前AdapterView已经对CursorAdapter设立了观察者)。最后AdapterView就会重新加载并显示新的数据。

在整个过程当中,我要做的就是在改变数据时发出信号,对封装数据的Cursor设置需要监视的信号。具体的说就是在query中调用Cursor# setNotificationUri,在insert、update、delete中调用ContentResolver# notifyChange。这里需要补充的是Cursor和ContentResolver的信号机制同样是通过观察者模式实现的。

其他的实现方式

这里要介绍的其他的实现方式,依旧是通过观察者模式实现的。区别在于是否使用ContentProvider和CursorLoader

不使用ContentProvider

在开发过程中,如果数据不用于应用之间的共享,使用ContentProvider似乎有一些多余。然而Android提供的CursorLoader的API必须通过ContentProvider才能实现数据加载和数据同步更新。但是你任然可以在不使用ContentProvider的情况下实现Cursor的自动更新。你需要做的只是在你的Loader中加入下面的代码

// 实例化一个全局的ForceLoadContentObserver
ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
@Override
public Cursor loadInBackground() {SQLiteDatabase database = mSqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(mTable, mColumns, mSelection, mSelectionArgs, mGroupBy,mHaving, mOrderBy);if (cursor != null) {cursor.getCount();// 对Cursor设立观察者cursor.registerContentObserver(mObserver);// 设置Cursor的观察信号cursor.setNotificationUri(getContext().getContentResolver(), mNotificationUri);}return cursor;
}

ForceLoadContentObserver是Loader的内部类。当观察到数据发生变化之后,该类会调用Loader# forceLoad,进而开始重新加载数据。另外你也可以直接使用我项目中的DatabaseLoader。该类是我参照CursorLoader编写的一个工具,通过它你可以绕过ContentProvider,直接请求Database。

不使用Loader

如果你不想要使用Loader(我非常不赞成你这么做),你可以通过如下的代码实现Cursor的同步更新。

// 使用CursorAdapter.FLAG_AUTO_REQUERY标志
adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_AUTO_REQUERY);
private void loadData() {SQLiteOpenHelper sqliteOpenHelper = ContactContract.getSqliteOpenHelper(this);SQLiteDatabase database = sqliteOpenHelper.getReadableDatabase();String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};Cursor cursor = database.query(ContactContract.CONTACT_TABLE, columns, null, null, null,null, null);//设置NotificationUricursor.setNotificationUri(this.getContentResolver(), ContactContract.SYNC_SIGNAL_URI);adapter.changeCursor(cursor);
}

这里的关键在于,在实例化CursorAdapter时使用了CursorAdapter.FLAGAUTOREQUERY标志。当使用该标志后,每当收到数据更新的消息,CursorAdapter就会自己调用CursorAdapter# requery重新加载数据。然而整个加载过程会再UI线程中发生,这很有可能会使得程序运行部流畅。正是因为这个原因该方法以及被Android设置为Deprecated了。因此如果有可能,我还是推荐你使用Loader。

ContactContract.java

package com.kohoh.cursorsyncdemo;import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.provider.BaseColumns;import com.google.common.base.Preconditions;
import com.google.common.base.Strings;/*** Created by kohoh on 14-11-3.*/
public class ContactContract implements BaseColumns {static final int DATABSE_VERSION = 1;static final String DATABASE_NAME = "contact.db";static final String CONTACT_TABLE = "contact";static final String NAME = "name";static final String PHONE = "phone";static final String AUTHORITY = "com.kohoh.cursorsyncdemo";static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);static final Uri CONTACT_URI = Uri.withAppendedPath(BASE_URI, "contact");static final Uri SYNC_SIGNAL_URI = Uri.withAppendedPath(BASE_URI, "SYNC_SIGNAL_URI");static public ContactDatabaseHelper getSqliteOpenHelper(Context context) {return new ContactDatabaseHelper(context);}static class ContactDatabaseHelper extends SQLiteOpenHelper {public ContactDatabaseHelper(Context context) {super(context, DATABASE_NAME, null, DATABSE_VERSION);}static public long addContact(SQLiteDatabase database, String name, int phone) {Preconditions.checkNotNull(database);Preconditions.checkNotNull(phone);Preconditions.checkArgument(!Strings.isNullOrEmpty(name));ContentValues contentValues = new ContentValues();contentValues.put(NAME, name);contentValues.put(PHONE, phone);return database.insert(CONTACT_TABLE, null, contentValues);}static public void deleteContact(Context context, long id) {Preconditions.checkNotNull(context);Preconditions.checkArgument(id >= 0);ContactContract.ContactDatabaseHelper databaseHelper = ContactContract.getSqliteOpenHelper(context);SQLiteDatabase databasea = databaseHelper.getWritableDatabase();String where = ContactContract._ID + " = ?";String[] whereArgs = {String.valueOf(id)};databasea.delete(ContactContract.CONTACT_TABLE, where, whereArgs);context.getContentResolver().notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE " + CONTACT_TABLE + "( " +_ID + " INTEGER PRIMARY KEY," +NAME + " TEXT," +PHONE + " INTERGER)");addContact(db, "aaa", 111);addContact(db, "bbb", 222);addContact(db, "ccc", 333);addContact(db, "ddd", 444);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}
}

ContactProvider.java

package com.kohoh.cursorsyncdemo;import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;/*** Created by kohoh on 14-11-3.*/
public class ContactProvider extends ContentProvider {private SQLiteOpenHelper sqLiteOpenHelper;private ContentResolver contentResolver;private UriMatcher uriMatcher;final private int DIR = 0;final private int ITEM = 1;@Overridepublic boolean onCreate() {sqLiteOpenHelper = ContactContract.getSqliteOpenHelper(getContext());contentResolver = getContext().getContentResolver();uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(ContactContract.AUTHORITY, "contact", DIR);uriMatcher.addURI(ContactContract.AUTHORITY, "contact/#", ITEM);return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {if (uriMatcher.match(uri) == ITEM) {return null;}SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(ContactContract.CONTACT_TABLE, projection, selection,selectionArgs, null, null, sortOrder);cursor.setNotificationUri(contentResolver, ContactContract.SYNC_SIGNAL_URI);return cursor;}@Overridepublic String getType(Uri uri) {switch (uriMatcher.match(uri)) {case ITEM:return "vnd.android.cursor.item/vnd.con.kohoh.cursorsyncdemo";case DIR:return "vnd.android.cursor.dir/vnd.con.kohoh.cursorsyncdemo";default:return null;}}@Overridepublic Uri insert(Uri uri, ContentValues values) {if (uriMatcher.match(uri) == ITEM) {return null;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();long id = database.insert(ContactContract.CONTACT_TABLE, null, values);if (id >= 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return uri.withAppendedPath(ContactContract.CONTACT_URI, String.valueOf(id));}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {if (uriMatcher.match(uri) == ITEM) {return 0;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.delete(ContactContract.CONTACT_TABLE, selection, selectionArgs);if (result > 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {if (uriMatcher.match(uri) == ITEM) {return 0;}SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();int result = database.update(ContactContract.CONTACT_TABLE, values, selection, selectionArgs);if (result > 0) {contentResolver.notifyChange(ContactContract.SYNC_SIGNAL_URI, null);}return result;}
}

DatabaseLoader.java

package com.kohoh.cursorsyncdemo;import android.content.AsyncTaskLoader;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;/*** Created by kohoh on 14-11-3.*/
public class DatabaseLoader extends AsyncTaskLoader<Cursor> {final ForceLoadContentObserver mObserver;Uri mNotificationUri;String mTable;String[] mColumns;String mSelection;String[] mSelectionArgs;String mGroupBy;String mHaving;String mOrderBy;SQLiteOpenHelper mSqLiteOpenHelper;Cursor mCursor;public DatabaseLoader(Context context) {super(context);this.mObserver = new ForceLoadContentObserver();}public DatabaseLoader(Context context, SQLiteOpenHelper sqLiteOpenHelper, Uri mNotificationUri,String mTable, String[] mColumns, String mSelection, String[] mSelectionArgs,String mGroupBy, String mHaving, String mOrderBy) {super(context);this.mNotificationUri = mNotificationUri;this.mTable = mTable;this.mColumns = mColumns;this.mSelection = mSelection;this.mSelectionArgs = mSelectionArgs;this.mGroupBy = mGroupBy;this.mHaving = mHaving;this.mOrderBy = mOrderBy;this.mSqLiteOpenHelper = sqLiteOpenHelper;this.mObserver = new ForceLoadContentObserver();}@Overridepublic Cursor loadInBackground() {SQLiteDatabase database = mSqLiteOpenHelper.getReadableDatabase();Cursor cursor = database.query(mTable, mColumns, mSelection, mSelectionArgs, mGroupBy,mHaving, mOrderBy);if (cursor != null) {cursor.getCount();cursor.registerContentObserver(mObserver);cursor.setNotificationUri(getContext().getContentResolver(), mNotificationUri);}return cursor;}@Overridepublic void deliverResult(Cursor cursor) {if (isReset()) {if (cursor != null) {cursor.close();}return;}Cursor oldCursor = mCursor;mCursor = cursor;if (isStarted()) {super.deliverResult(cursor);}if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {oldCursor.close();}}@Overrideprotected void onStartLoading() {if (mCursor != null) {deliverResult(mCursor);}if (takeContentChanged() || mCursor == null) {forceLoad();}}@Overrideprotected void onStopLoading() {// Attempt to cancel the current load task if possible.cancelLoad();}@Overridepublic void onCanceled(Cursor cursor) {if (cursor != null && !cursor.isClosed()) {cursor.close();}}@Overrideprotected void onReset() {super.onReset();onStopLoading();if (mCursor != null && !mCursor.isClosed()) {mCursor.close();}mCursor = null;}public SQLiteOpenHelper getSqLiteOpenHelper() {return mSqLiteOpenHelper;}public void setSqLiteOpenHelper(SQLiteOpenHelper mSqLiteOpenHelper) {this.mSqLiteOpenHelper = mSqLiteOpenHelper;}public Uri getNotificationUri() {return mNotificationUri;}public void setNotificationUri(Uri mNotificationUri) {this.mNotificationUri = mNotificationUri;}public String getTable() {return mTable;}public void setTable(String mTable) {this.mTable = mTable;}public String[] getColumns() {return mColumns;}public void setColumns(String[] mColumns) {this.mColumns = mColumns;}public String getSelection() {return mSelection;}public void setSelection(String mSelection) {this.mSelection = mSelection;}public String[] getSelectionArgs() {return mSelectionArgs;}public void setSelectionArgs(String[] mSelectionArgs) {this.mSelectionArgs = mSelectionArgs;}public String getGroupBy() {return mGroupBy;}public void setGroupBy(String mGroupBy) {this.mGroupBy = mGroupBy;}public String getHaving() {return mHaving;}public void setHaving(String mHaving) {this.mHaving = mHaving;}public String getOrderBy() {return mOrderBy;}public void setOrderBy(String mOrderBy) {this.mOrderBy = mOrderBy;}
}

use provider

package com.kohoh.cursorsyncdemo;import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);listView.setAdapter(adapter);getLoaderManager().initLoader(0, null, this);registerForContextMenu(listView);}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {String[] projection = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};return new CursorLoader(this, ContactContract.CONTACT_URI, projection, null, null, null);}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {adapter.changeCursor(data);}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {}
}

use database

package com.kohoh.cursorsyncdemo;import android.app.Activity;
import android.app.LoaderManager;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);listView.setAdapter(adapter);getLoaderManager().initLoader(0, null, this);registerForContextMenu(listView);}@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};return new DatabaseLoader(this,ContactContract.getSqliteOpenHelper(this),ContactContract.SYNC_SIGNAL_URI,ContactContract.CONTACT_TABLE,columns,null,null,null,null,null);}@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {adapter.changeCursor(data);}@Overridepublic void onLoaderReset(Loader<Cursor> loader) {}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);}
}

use adapter

package com.kohoh.cursorsyncdemo;import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;public class CursorSyncDemo extends Activity {private ListView listView;private SimpleCursorAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_cursor_sync_demo);String[] from = {ContactContract.NAME, ContactContract.PHONE};int[] to = {R.id.name, R.id.phone};listView = (ListView) findViewById(R.id.lv);adapter = new SimpleCursorAdapter(this, R.layout.contact_item, null, from, to,CursorAdapter.FLAG_AUTO_REQUERY);listView.setAdapter(adapter);loadData();registerForContextMenu(listView);}private void loadData() {SQLiteOpenHelper sqliteOpenHelper = ContactContract.getSqliteOpenHelper(this);SQLiteDatabase database = sqliteOpenHelper.getReadableDatabase();String[] columns = {ContactContract._ID, ContactContract.NAME, ContactContract.PHONE};Cursor cursor = database.query(ContactContract.CONTACT_TABLE, columns, null, null, null,null, null);cursor.setNotificationUri(this.getContentResolver(), ContactContract.SYNC_SIGNAL_URI);adapter.changeCursor(cursor);}@Overridepublic boolean onContextItemSelected(MenuItem item) {switch (item.getItemId()) {case R.id.delete:AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();ContactContract.ContactDatabaseHelper.deleteContact(this, menuInfo.id);return true;default:return super.onContextItemSelected(item);}}@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.contact_item_menu, menu);}
}

Android Cursor自动更新的实现和原理相关推荐

  1. android 360自动更新,Android应用自动更新功能的实现!!!

    大家好,发现半年没有更新博客了,最近一直都比较忙,决定在凌晨 英超 阿森纳VS富勒姆 中场休息的时候,给大家分享Android里应用版本更新功能这一块的实现. 一个好的应用软件都是需要好的维护,从初出 ...

  2. Android 程序自动更新功能模块实现

    2019独角兽企业重金招聘Python工程师标准>>> Android 程序自动更新功能模块实现 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程 ...

  3. Android应用自动更新功能实现使用AsyncTask!

    为什么80%的码农都做不了架构师?>>>    我所开发应用不是面向大众的应用,所以无法放到应用市场去让大家下载,然后通过应用市场更新.所以我必要做一个应用自动更新功能.但是不难,T ...

  4. Android - 软件自动更新的实现

    Android - 软件自动更新的实现 2012年11月18日 天气慢慢变凉了,给位亲,注意保暖啊. 接触到一个很实用的技术,那就是软件自动更新.一般开发者是通过自行在应用平台添加更新版本的apk.这 ...

  5. Android App自动更新解决方案(DownloadManager)

    Android App自动更新解决方案(DownloadManager) 参考文章: (1)Android App自动更新解决方案(DownloadManager) (2)https://www.cn ...

  6. android通知栏应用程序更新,Android App自动更新之通知栏下载

    本文实例为大家分享了Android App自动更新通知栏下载的具体代码,供大家参考,具体内容如下 版本更新说明 这里有调用UpdateService启动服务检查下载安装包等 1. 文件下载,下完后写入 ...

  7. android自动更新demo,Android程序自动更新功能模块的实现方法【附完整demo源码下载】...

    本文实例讲述了Android程序自动更新功能模块的实现方法.分享给大家供大家参考,具体如下: 在程序启动的时候检测服务器上有没有对应版本更新,如果有更新,提示用户是否更新. 在程序启动的时候首先调用更 ...

  8. Android Studio 自动更新失败解决办法

    Android Studio 自动更新失败解决办法 Dec 26th, 2014 | Comments 昨天在G+中看到Android Studio又有更新了就心血来潮想去更新体验一下,可是无论我怎么 ...

  9. Android APP 自动更新实现(适用Android9.0)

    Android App自动更新基本上是每个App都需具备的功能,参考网上各种资料,自己整理了下,先来看看大致的界面: 一.实现思路: 1.发布Android App时,都会生成output-metad ...

最新文章

  1. Qt文档阅读笔记-单元测试中构建标准检测程序(CPU、Linux性能、内存泄漏等)
  2. 深度学习——用向量重新组织输入数据
  3. WORD图片批量居中?
  4. 献策企业:制定优秀移动化企业七步骤
  5. 互联网产品开发中的“快”字诀
  6. 8255A置位复位控制字
  7. 4.6 TF常用功能模块
  8. html css js 注释符号,js 注释怎么写 javascript注释格式|js注释
  9. 2022重庆大学877软件工程考研经验贴
  10. 五大主流浏览器及四大内核
  11. B站视频下载 bilibili 哔哩哔哩
  12. html link canonical
  13. ADC0809芯片简介
  14. 小米java模拟器_Android沉浸式简单实现(暂未测试全部机型,根据资料修改,模拟器,小米6,,荣耀做了简单测试)...
  15. 如何将图像保存至计算机G7X,佳能官方教程:如何将G7X通过WiFi与电脑连接.pdf
  16. Matplotlib做动图(基础版)
  17. unity新特性_新的Unity论坛
  18. Linux无法联网解决方案
  19. 中序遍历二叉树-Java实现
  20. (戒急用忍)本来是要展望2019的,没想到重点不由自主的写成了回顾前几年的事情了

热门文章

  1. SD-WAN与传统WAN设备相比
  2. 物联网的发展之—Vecloud微云
  3. oracle触发器高级教程
  4. 蓝图Blueprint
  5. Python直接赋值,浅拷贝和深度拷贝
  6. 删除弹出提示框_MVC
  7. hdu_5701_中位数计数
  8. Coreseek:部门查询和增量索引代替实时索引
  9. 两种比较不错的密码修改方案
  10. windows7 设置 Local Settings权限为可以访问