文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6967204

在Android系统中,不同的应用程序是不能直接读写对方的数据文件的,如果它们想共享数据的话,只能通过 Content Provider组件来实现。那么,Content Provider组件又是如何突破应用程序边界权限控制来实现在不同的应用程序之间共享数据的呢?在前面的文章中,我们已经简要介绍过它是通过 Binder进程间通信机制以及匿名共享内存机制来实现的,在本文中,我们将详细分析它的数据共享原理。

Android应用程序之间不能直接访问对方的数据文件的障碍在于每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋 予给自己所属的用户,因此,就限制了应用程序之间相互读写数据的操作,关于Android应用程序的权限问题,具体可以参考前面一篇文章Android应用程序组件Content Provider简要介绍和学习计划。通过前面Android进程间通信(IPC)机制Binder简要介绍和学习计划等 一系列文章的学习,我们知道,Binder进程间通信机制可以突破了以应用程序为边界的权限控制来实现在不同应用程序之间传输数据,而Content Provider组件在不同应用程序之间共享数据正是基于Binder进程间通信机制来实现的。虽然Binder进程间通信机制突破了以应用程序为边界的 权限控制,但是它是安全可控的,因为数据的访问接口是由数据的所有者来提供的,换句话来说,就是数据提供方可以在接口层来实现安全控制,决定哪些数据是可 以读,哪些数据可以写。虽然Content Provider组件本身也提供了读写权限控制,但是它的控制粒度是比较粗的,如果有需要,我们还是可以在接口访问层做更细粒度的权限控制以达到数据安全 的目的。

Binder进程间通信机制虽然打通了应用程序之间共享数据的通道,但是还有一个问题需要解决,那就是数据要以什么来作来媒介来传输。我们知道,应用程 序采用Binder进程间通信机制进行通信时,要传输的数据都是采用函数参数的形式进行的,对于一般的进程间调来来说,这是没有问题的,然而,对于应用程 序之间的共享数据来说,它们的数据量可能是非常大的,如果还是简单的用函数参数的形式来传递,效率就会比较低下。通过前面Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划等一系列文章的学习,我们知道,在应用程序进程之间以匿名共享内存的方式来传输数据效率是非常高的,因为它们之间只需要传递一个文件描述符就可以了。因此,Content Provider组件在不同应用程序之间传输数据正是基于匿名共享内存机制来实现的。

在继续分析Content Provider组件在不同应用程序之间共享数据的原理之前,我们假设应用程序之间需要共享的数据是保存在数据库(SQLite)中的,因此,接下来的分 析都是基于SQLite数据库游标(SQLiteCursor)来进行的。SQLiteCursor在共享数据的传输过程中发挥着重要的作用,因此,我们 先来它和其它相关的类的关系图,如下所示:

首先在第三方应用程序这一侧,当它需要访问Content Provider中的数据时,它会在本进程中创建一个CursorWindow对象,它在内部创建了一块匿名共享内存,同时,它实现了Parcel接口, 因此它可以在进程间传输。接下来第三方应用程序把这个CursorWindow对象(连同它内部的匿名共享内存文件描述符)通过Binder进程间调用传 输到Content Provider这一侧。这个匿名共享内存文件描述符传输到Binder驱动程序的时候,Binder驱动程序就会在目标进程(即Content Provider所在的进程)中创建另一个匿名共享文件描述符,指向前面已经创建好的匿名共享内存,因此,就实现了在两个进程中共享同一块匿名内存,这个 过程具体可以参考Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析一文。

在Content Provider这一侧,利用在Binder驱动程序为它创建好的这个匿名共享内存文件描述符,在本进程中创建了一个CursorWindow对象。现 在,Content Provider开始要从本地中从数据库中查询第三方应用程序想要获取的数据了。Content Provider首先会创建一个SQLiteCursor对象,即SQLite数据库游标对象,它继承了AbstractWindowedCursor 类,后者又继承了AbstractCursor类,而AbstractCursor类又实现了CrossProcessCursor和Cursor接口。 其中,最重要的是在AbstractWindowedCursor类中,有一个成员变量mWindow,它的类型为CursorWindow,这个成员变 量是通过AbstractWindowedCursor的子类SQLiteCursor的setWindow成员函数来设置的。这个 SQLiteCursor对象设置好了父类AbstractWindowedCursor类的mWindow成员变量之后,它就具有传输数据的能力了,因 为这个mWindow对象内部包含一块匿名共享内存。此外,这个SQLiteCursor对象的内部有两个成员变量,一个是SQLite数据库对象 mDatabase,另外一个是SQLite数据库查询对象mQuery。SQLite数据库查询对象mQuery的类型为SQLiteQuery,它继 承了SQLiteProgram类,后者又继承了SQLiteClosable类。SQLiteProgram类代表一个数据库存查询计划,它的成员变量 mCompiledSql包含了一个已经编译好的SQL查询语句,SQLiteCursor对象就是利用这个编译好的SQL查询语句来获得数据的,但是它 并不是马上就去获取数据的,而是等到需要时才去获取。

那么,要等到什么时候才会需要获取数据呢?一般来说,如果第三方应用程序在请求Content Provider返回数据时,如果指定了要返回关于这些数据的元信息时,例如数据条目的数量,那么Content Provider在把这个SQLiteCursor对象返回给第三方应用程序之前,就会去获取数据,因为只有获取了数据之后,才知道数据条目的数量是多 少。SQLiteCursor对象通过调用成员变量mQuery的fillWindow成员函数来把从SQLite数据库中查询得到的数据保存其父类 AbstractWindowedCursor的成员变量mWindow中去,即保存到第三方应用程序创建的这块匿名共享内存中去。如果第三方应用程序在 请求Content Provider返回数据时,没有指定要返回关于这些数据的元信息,那么,就要等到第三方应用程序首次调用这个从Content Provider处返回的SQLiteCursor对象的数据获取方法时,才会真正执行从数据库存中查询数据的操作,例如调用了SQLiteCursor 对象的getCount或者moveToFirst成员函数时。这是一种数据懒加载机制,需要的时候才去加载,这样就提高了数据传输过程中的效率。

上面说到,Content Provider向第三方应用程序返回的数据实际上是一个SQLiteCursor对象,那么,这个SQLiteCursor对象是如何传输到第三方应用 程序的呢?因为它本身并不是一个Binder对象,我们需要对它进行适配一下。首先,Content Provider会根据这个SQLiteCursor对象来创建一个CursorToBulkCursorAdaptor适配器对象,这个适配器对象是一 个Binder对象,因此,它可以在进程间传输,同时,它实现了IBulkCursor接口。Content Provider接着就通过Binder进程间通信机制把这个CursorToBulkCursorAdaptor对象返回给第三方应用程序,第三方应用 程序得到了这个CursorToBulkCursorAdaptor之后,再在本地创建一个BulkCursorToCursorAdaptor对象,这 个BulkCursorToCursorAdaptor对象的继承结构和SQLiteCursor对象是一样的,不过,它没有设置父类 AbstractWindowedCursor的mWindow成员变量,因此,它只可以通过它内部的 CursorToBulkCursorAdaptor对象引用来访问匿名共享内存中的数据,即通过访问Content Provider这一侧的SQLiteCursor对象来访问共享数据。

上面描述的数据共享模型还是比较复杂的,一下子理解不了也不要紧,下面我们还会结合第三方应用程序和Content Provider传输共享数据的完整过程来进一步分析Content Provider的数据共享原理,到时候再回过头来看这个数据共享模型就会清晰很多了。在接下来的内容中,我们就继续以Android应用程序组件Content Provider应用实例一文的例子来分析Content Provider在不同应用程序之间共享数据的原理。在Android应用程序组件Content Provider应用实例这 篇文章介绍的应用程序Article中,它的主窗口MainActivity是通过调用它的内部ArticlesAdapter对象的 getArticleByPos成员函数来把ArticlesProvider中的文章信息条目一条一条地取回来显示在ListView中的,在这篇文章 中,我们就从ArticlesAdapter类的getArticleByPos函数开始,一步一步地分析第三方应用程序Article从ArticlesProvider这个Content Provider中获取数据的过程。同样,我们先来看看这个过程的序列图,然后再详细分析每一个步骤:

Step 1. ArticlesAdapter.getArticleByPos

这个函数定义在前面一篇文章Android应用程序组件Content Provider应用实例介绍的应用程序Artilce源代码工程目录下,在文件为packages/experimental/Article/src/shy/luo/article/ArticlesAdapter.java中:

  1. public class ArticlesAdapter {
  2. ......
  3. private ContentResolver resolver = null;
  4. public ArticlesAdapter(Context context) {
  5. resolver = context.getContentResolver();
  6. }
  7. ......
  8. public Article getArticleByPos(int pos) {
  9. Uri uri = ContentUris.withAppendedId(Articles.CONTENT_POS_URI, pos);
  10. String[] projection = new String[] {
  11. Articles.ID,
  12. Articles.TITLE,
  13. Articles.ABSTRACT,
  14. Articles.URL
  15. };
  16. Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);
  17. if (!cursor.moveToFirst()) {
  18. return null;
  19. }
  20. int id = cursor.getInt(0);
  21. String title = cursor.getString(1);
  22. String abs = cursor.getString(2);
  23. String url = cursor.getString(3);
  24. return new Article(id, title, abs, url);
  25. }
  26. }

这个函数通过应用程序上下文的ContentResolver接口resolver的query函数来获得与
Articles.CONTENT_POS_URI这个URI对应的文章信息条目。常量Articles.CONTENT_POS_URI是在应用程序
ArticlesProvider中定义的,它的值为“content://shy.luo.providers.articles/pos”,通过调用
ContentUris.withAppendedId函数来在后面添加了一个整数,表示要获取指定位置的文章信息条目。这个位置是指ArticlesProvider这个Content Provider中的所有文章信息条目按照Articles.DEFAULT_SORT_ORDER来排序后得到的位置的,常量Articles.DEFAULT_SORT_ORDER也是在应用程序ArticlesProvider中定义的,它的值为“_id asc”,即按照文章信息的ID值从小到大来排列。

Step 2. ContentResolver.query

这个函数定义在frameworks/base/core/java/android/content/ContentResolver.java文件中:

  1. public abstract class ContentResolver {
  2. ......
  3. public final Cursor query(Uri uri, String[] projection,
  4. String selection, String[] selectionArgs, String sortOrder) {
  5. IContentProvider provider = acquireProvider(uri);
  6. if (provider == null) {
  7. return null;
  8. }
  9. try {
  10. ......
  11. Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
  12. ......
  13. return new CursorWrapperInner(qCursor, provider);
  14. } catch (RemoteException e) {
  15. ......
  16. } catch(RuntimeException e) {
  17. ......
  18. }
  19. }
  20. ......
  21. }

这个函数首先通过调用acquireProvider函数来获得与参数uri对应的Content
Provider接口,然后再通过这个接口的query函数来获得相应的数据。我们先来看看acquireProvider函数的实现,再回过头来分析这
个Content Provider接口的query函数的实现。

Step 3. ContentResolver.acquireProvider

Step 4. ApplicationContentResolver.acquireProvider

Step 5. ActivityThread.acquireProvider

Step 6. ActivityThread.getProvider

从Step 3到Step 6是获取与上面Step 2中传进来的参数uri对应的Content Provider接口的过程。在前面一篇文章Android应用程序组件Content Provider的启动过程源代码分析中,
我们已经详细介绍过这个过程了,这里不再详述。不过这里我们假设,这个Content Provider接口之前已经创建好了,因此,在Step
6的ActivityThread.getProvider函数中,通过getExistingProvider函数就把之前已经好的Content Provider接口返回来了。

回到Step 2中的ContentResolver.query函数中,它继续调用这个返回来的Content Provider接口来获取数据。从这篇文章Android应用程序组件Content Provider的启动过程源代码分析中,我们知道,这个Content Provider接口实际上是一个在ContentProvider类的内部所创建的一个Transport对象的远程接口。这个Transport类继承了ContentProviderNative类,是一个Binder对象的Stub类,因此,接下来就会进入到这个Binder对象的Proxy类ContentProviderProxy中执行query函数。

Step 7. ContentProviderProxy.query

这个函数定义在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

  1. final class ContentProviderProxy implements IContentProvider {
  2. ......
  3. public Cursor query(Uri url, String[] projection, String selection,
  4. String[] selectionArgs, String sortOrder) throws RemoteException {
  5. //TODO make a pool of windows so we can reuse memory dealers
  6. CursorWindow window = new CursorWindow(false /* window will be used remotely */);
  7. BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
  8. IBulkCursor bulkCursor = bulkQueryInternal(
  9. url, projection, selection, selectionArgs, sortOrder,
  10. adaptor.getObserver(), window,
  11. adaptor);
  12. if (bulkCursor == null) {
  13. return null;
  14. }
  15. return adaptor;
  16. }
  17. ......
  18. }

这个函数首先会创建一个CursorWindow对象,前面已经说过,这个CursorWindow对象包含了一块匿名共享内存,它的作用是把这块匿名
共享内存通过Binder进程间通信机制传给Content Proivder,好让Content
Proivder在里面返回所请求的数据。下面我们就先看看这个CursorWindow对象的创建过程,重点关注它是如何在内部创建匿名共享内存的,然
后再回过头来看看它调用bulkQueryInternal函数来做了些什么事情。

CursorWindow类定义在frameworks/base/core/java/android/database/CursorWindow.java文件中,我们来看看它的构造函数的实现:

  1. public class CursorWindow extends SQLiteClosable implements Parcelable {
  2. ......
  3. private int nWindow;
  4. ......
  5. public CursorWindow(boolean localWindow) {
  6. ......
  7. native_init(localWindow);
  8. }
  9. ......
  10. }

它主要调用本地方法native_init来执行初始化的工作,主要就是创建匿名共享内存了,传进来的参数localWindow为false,表示这
个匿名共享内存只能通过远程调用来访问,即前面我们所说的,通过Content
Proivder返回来的Cursor接口来访问这块匿名共享内存里面的数据。

Step 8. CursorWindow.native_init

这是一个JNI方法,它对应定义在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中的native_init_empty函数:

  1. static JNINativeMethod sMethods[] =
  2. {
  3. /* name, signature, funcPtr */
  4. {"native_init", "(Z)V", (void *)native_init_empty},
  5. ......
  6. };

函数native_init_empty的定义如下所示:

  1. static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
  2. {
  3. ......
  4. CursorWindow * window;
  5. window = new CursorWindow(MAX_WINDOW_SIZE);
  6. ......
  7. if (!window->initBuffer(localOnly)) {
  8. ......
  9. }
  10. ......
  11. SET_WINDOW(env, object, window);
  12. }

这个函数在C++层创建了一个CursorWindow对象,然后通过调用SET_WINDOW宏来把这个C++层的CursorWindow对象与Java层的CursorWindow对象关系起来:

  1. #define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))

这里的gWindowField即定义为Java层的CursorWindow对象中的nWindow成员变量:

  1. static jfieldID gWindowField;
  2. ......
  3. int register_android_database_CursorWindow(JNIEnv * env)
  4. {
  5. jclass clazz;
  6. clazz = env->FindClass("android/database/CursorWindow");
  7. ......
  8. gWindowField = env->GetFieldID(clazz, "nWindow", "I");
  9. ......
  10. }

这种用法在Android应用程序框架层中非常普遍。

下面我们重点关注C++层的CursorWindow对象的initBuffer函数的实现。

Step 9. CursorWindow.initBuffer

C++层的CursorWindow类定义在frameworks/base/core/jni/CursorWindow.cpp文件中:

  1. bool CursorWindow::initBuffer(bool localOnly)
  2. {
  3. ......
  4. sp<MemoryHeapBase> heap;
  5. heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
  6. if (heap != NULL) {
  7. mMemory = new MemoryBase(heap, 0, mMaxSize);
  8. if (mMemory != NULL) {
  9. mData = (uint8_t *) mMemory->pointer();
  10. if (mData) {
  11. mHeader = (window_header_t *) mData;
  12. mSize = mMaxSize;
  13. ......
  14. }
  15. }
  16. ......
  17. } else {
  18. ......
  19. }
  20. }

这里我们就可以很清楚地看到,在CursorWindow类的内部有一个成员变量mMemory,它的类型是MemoryBase。MemoryBase类为我们封装了匿名共享内存的访问以及在进程间的传输等问题,具体可以参考前面一篇文章Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析,这里就不再详述了。

通过Step 8和Step 9两步,用来在第三方应用程序和Content
Provider之间传输数据的媒介就准备好了,我们回到Step 7中,看看系统是如何把这个匿名共享存传递给Content
Provider使用的。在Step 7中,最后调用bulkQueryInternal函数来进一步操作。

Step 10. ContentProviderProxy.bulkQueryInternal

这个函数定义在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

  1. final class ContentProviderProxy implements IContentProvider
  2. {
  3. ......
  4. private IBulkCursor bulkQueryInternal(
  5. Uri url, String[] projection,
  6. String selection, String[] selectionArgs, String sortOrder,
  7. IContentObserver observer, CursorWindow window,
  8. BulkCursorToCursorAdaptor adaptor) throws RemoteException {
  9. Parcel data = Parcel.obtain();
  10. Parcel reply = Parcel.obtain();
  11. data.writeInterfaceToken(IContentProvider.descriptor);
  12. url.writeToParcel(data, 0);
  13. int length = 0;
  14. if (projection != null) {
  15. length = projection.length;
  16. }
  17. data.writeInt(length);
  18. for (int i = 0; i < length; i++) {
  19. data.writeString(projection[i]);
  20. }
  21. data.writeString(selection);
  22. if (selectionArgs != null) {
  23. length = selectionArgs.length;
  24. } else {
  25. length = 0;
  26. }
  27. data.writeInt(length);
  28. for (int i = 0; i < length; i++) {
  29. data.writeString(selectionArgs[i]);
  30. }
  31. data.writeString(sortOrder);
  32. data.writeStrongBinder(observer.asBinder());
  33. window.writeToParcel(data, 0);
  34. // Flag for whether or not we want the number of rows in the
  35. // cursor and the position of the "_id" column index (or -1 if
  36. // non-existent).  Only to be returned if binder != null.
  37. final boolean wantsCursorMetadata = (adaptor != null);
  38. data.writeInt(wantsCursorMetadata ? 1 : 0);
  39. mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
  40. DatabaseUtils.readExceptionFromParcel(reply);
  41. IBulkCursor bulkCursor = null;
  42. IBinder bulkCursorBinder = reply.readStrongBinder();
  43. if (bulkCursorBinder != null) {
  44. bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
  45. if (wantsCursorMetadata) {
  46. int rowCount = reply.readInt();
  47. int idColumnPosition = reply.readInt();
  48. if (bulkCursor != null) {
  49. adaptor.set(bulkCursor, rowCount, idColumnPosition);
  50. }
  51. }
  52. }
  53. data.recycle();
  54. reply.recycle();
  55. return bulkCursor;
  56. }
  57. ......
  58. }

这个函数有点长,不过它的逻辑很简单,就是把查询参数都写到一个Parcel对象data中去,然后通过下面Binder进程间通信机制把查询请求传给Content Provider处理:

  1. mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);

从这个Binder调用返回以后,就会得到一个IBulkCursor接口,它是一个Binder引用,实际是指向在Content
Provider这一侧创建的一个CursorToBulkCursorAdaptor对象,后面我们将会看到。有了这个IBulkCursor接口之
后,我们就可以通过Binder进程间调用来访问从Content
Provider中查询得到的数据了。这个IBulkCursor接口最终最设置了上面Step
7中创建的BulkCursorToCursorAdaptor对象adaptor中去:

  1. adaptor.set(bulkCursor, rowCount, idColumnPosition);

BulkCursorToCursorAdaptor类实现了Cursor接口,因此,我们可以通过Curosr接口来访问这些查询得到的共享数据。

在前面把查询参数写到Parcel对象data中去的过程中,有两个步骤是比较重要的,分别下面这段执行语句:

  1. window.writeToParcel(data, 0);
  2. // Flag for whether or not we want the number of rows in the
  3. // cursor and the position of the "_id" column index (or -1 if
  4. // non-existent).  Only to be returned if binder != null.
  5. final boolean wantsCursorMetadata = (adaptor != null);
  6. data.writeInt(wantsCursorMetadata ? 1 : 0);

调用window.writeToParcel是把window对象内部的匿名共享内存块通过Binder进程间通信机制传输给Content
Provider来使用;而当传进来的参数adaptor不为null时,就会往data中写入一个整数1,表示让Content
Provider把查询得到数据的元信息一起返回来,例如数据的行数、数据行的ID列的索引位置等信息,这个整数值会促使Content
Provider把前面说的IBulkCursor接口返回给第三方应用程序之前,真正执行一把数据库查询操作,后面我们将看到这个过程。

现在,我们重点来关注一下CursorWindow类的writeToParcel函数,看看它是如何把它内部的匿名共享内存对象写到数据流data中去的。

Step 11. CursorWindow.writeToParcel

这个函数定义在frameworks/base/core/java/android/database/CursorWindow.java文件中:

  1. public class CursorWindow extends SQLiteClosable implements Parcelable {
  2. ......
  3. public void writeToParcel(Parcel dest, int flags) {
  4. ......
  5. dest.writeStrongBinder(native_getBinder());
  6. ......
  7. }
  8. ......
  9. }

这个函数最主要的操作就是往数据流dest写入一个Binder对象,这个Binder对象是通过调用本地方法native_getBinder来得到的。

Step 12. CursorWindow.native_getBinder

这个函数定义在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中:

  1. static jobject native_getBinder(JNIEnv * env, jobject object)
  2. {
  3. CursorWindow * window = GET_WINDOW(env, object);
  4. if (window) {
  5. sp<IMemory> memory = window->getMemory();
  6. if (memory != NULL) {
  7. sp<IBinder> binder = memory->asBinder();
  8. return javaObjectForIBinder(env, binder);
  9. }
  10. }
  11. return NULL;
  12. }

在前面的Step
8中,我们在C++层创建了一个CursorWindow对象,这个对象保存在Java层创建的CursorWindow对象的成员变量nWindow
中,这里通过GET_WINDOW宏来把这个在C++层创建的CurosrWindow对象返回来:

  1. #define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))

获得了这个CursorWindow对象以后,就调用它的getMemory函数来获得一个IMemory接口,这是一个Binder接口,具体可以参考前面一篇文章Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析

Step 13. CursorWindow.getMemory

这个函数定义在frameworks/base/core/jni/CursorWindow.h文件中:

  1. class CursorWindow
  2. {
  3. public:
  4. ......
  5. sp<IMemory>         getMemory() {return mMemory;}
  6. ......
  7. }

这个CursorWindow对象的成员变量mMemory就是在前面Step 9中创建的了。

这样,在第三方应用程序这一侧创建的匿名共享存对象就可以传递给Content Provider来使用了。回到前面的Step
10中,所有的参数都就准备就绪以后,就通过Binder进程间通信机制把数据查询请求发送给相应的Content
Proivder了。这个请求是在ContentProviderNative类的onTransact函数中响应的。

Step 14. ContentProviderNative.onTransact

这个函数定义在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

  1. abstract public class ContentProviderNative extends Binder implements IContentProvider {
  2. ......
  3. @Override
  4. public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
  5. throws RemoteException {
  6. try {
  7. switch (code) {
  8. case QUERY_TRANSACTION:
  9. {
  10. data.enforceInterface(IContentProvider.descriptor);
  11. Uri url = Uri.CREATOR.createFromParcel(data);
  12. // String[] projection
  13. int num = data.readInt();
  14. String[] projection = null;
  15. if (num > 0) {
  16. projection = new String[num];
  17. for (int i = 0; i < num; i++) {
  18. projection[i] = data.readString();
  19. }
  20. }
  21. // String selection, String[] selectionArgs...
  22. String selection = data.readString();
  23. num = data.readInt();
  24. String[] selectionArgs = null;
  25. if (num > 0) {
  26. selectionArgs = new String[num];
  27. for (int i = 0; i < num; i++) {
  28. selectionArgs[i] = data.readString();
  29. }
  30. }
  31. String sortOrder = data.readString();
  32. IContentObserver observer = IContentObserver.Stub.
  33. asInterface(data.readStrongBinder());
  34. CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
  35. // Flag for whether caller wants the number of
  36. // rows in the cursor and the position of the
  37. // "_id" column index (or -1 if non-existent)
  38. // Only to be returned if binder != null.
  39. boolean wantsCursorMetadata = data.readInt() != 0;
  40. IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
  41. selectionArgs, sortOrder, observer, window);
  42. reply.writeNoException();
  43. if (bulkCursor != null) {
  44. reply.writeStrongBinder(bulkCursor.asBinder());
  45. if (wantsCursorMetadata) {
  46. reply.writeInt(bulkCursor.count());
  47. reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
  48. bulkCursor.getColumnNames()));
  49. }
  50. } else {
  51. reply.writeStrongBinder(null);
  52. }
  53. return true;
  54. }
  55. ......
  56. }
  57. } catch (Exception e) {
  58. DatabaseUtils.writeExceptionToParcel(reply, e);
  59. return true;
  60. }
  61. return super.onTransact(code, data, reply, flags);
  62. }
  63. ......
  64. }

这一步其实就是前面Step 10的逆操作,把请求参数从数据流data中读取出来。这里我们同样是重点关注下面这两个参数读取的步骤:

  1. CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
  2. // Flag for whether caller wants the number of
  3. // rows in the cursor and the position of the
  4. // "_id" column index (or -1 if non-existent)
  5. // Only to be returned if binder != null.
  6. boolean wantsCursorMetadata = data.readInt() != 0;

通过调用CursorWindow.CREATOR.createFromParcel函数来从数据流data中重建一个本地的
CursorWindow对象;接着又将数据流data的下一个整数值读取出来,如果这个整数值不为0,变量wantsCursorMetadata的值
就为true,说明Content
Provider在返回IBulkCursor接口给第三方应用程序之前,要先实际执行一把数据库查询操作,以便把结果数据的元信息返回给第三方应用程
序。

通过下面的代码我们可以看到,调用bulkQuery函数之后,就得到了一个IBulkCursor接口,这表示要返回的数据准备就绪了,但是这时候实
际上还没有把结果数据从数据库中提取出来,而只是准备好了一个SQL查询计划,等到真正要使用这些结果数据时,系统才会真正执行查询数据库的操作:

  1. if (wantsCursorMetadata) {
  2. reply.writeInt(bulkCursor.count());
  3. ......
  4. }

在将这个IBulkCursor接口返回给第三方应用程序之前,如果发现wantsCursorMetadata的值就为true,就会调用它的count函数来获得结果数据的总行数,这样就会导致系统真正去执行数据库查询操作,并把结果数据保存到前面得到的CursorWindow对象中的匿名共享内存中去。

下面我们就重点关注CursorWindow.CREATOR.createFromParcel函数是如何从数据流data中在本地构造一个CursorWindow对象的。

Step 15. CursorWindow.CREATOR.createFromParcel

这个函数定义在frameworks/base/core/java/android/database/CursorWindow.java文件中:

  1. public class CursorWindow extends SQLiteClosable implements Parcelable {
  2. ......
  3. private CursorWindow(Parcel source) {
  4. IBinder nativeBinder = source.readStrongBinder();
  5. ......
  6. native_init(nativeBinder);
  7. }
  8. ......
  9. public static final Parcelable.Creator<CursorWindow> CREATOR
  10. = new Parcelable.Creator<CursorWindow>() {
  11. public CursorWindow createFromParcel(Parcel source) {
  12. return new CursorWindow(source);
  13. }
  14. ......
  15. };
  16. ......
  17. }

在创建CursorWindow对象的过程中,首先是从数据流source中将在前面Step
10中写入的Binder接口读取出来,然后使用这个Binder接口来初始化这个CursorWindow对象,通过前面的Step
13,我们知道,这个Binder接口的实际类型为IMemory,它封装了对匿名共享内存的访问操作。初始化这个匿名共享内存对象的操作是由本地方法
native_init函数来实现的,下面我们就看看它的实现。

Step 16. CursorWindow.native_init

这个函数定义在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中,对应的函数为native_init_memory函数:

  1. static JNINativeMethod sMethods[] =
  2. {
  3. ......
  4. {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
  5. };

函数native_init_memory的实现如下所示:

  1. static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
  2. {
  3. sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
  4. ......
  5. CursorWindow * window = new CursorWindow();
  6. ......
  7. if (!window->setMemory(memory)) {
  8. ......
  9. }
  10. ......
  11. SET_WINDOW(env, object, window);
  12. }

函数首先是将前面Step
15中传进来的Binder接口转换为IMemory接口,接着创建一个C++层的CursorWindow对象,再接着用这个IMemory接口来初始
化这个C++层的CursorWindow对象,最后像前面的Step
8一样,通过宏SET_WINDOW把这个C++层的CursorWindow对象和前面在Step
15中创建的Java层CursorWindow对象关联起来。

下面我们就重点关注CursorWindow类的setMemory函数的实现,看看它是如何使用这个IMemory接口来初始化其内部的匿名共享内存对象的。

Step 17. CursorWindow.setMemory

这个函数定义在frameworks/base/core/jni/CursorWindow.cpp文件中:

  1. bool CursorWindow::setMemory(const sp<IMemory>& memory)
  2. {
  3. mMemory = memory;
  4. mData = (uint8_t *) memory->pointer();
  5. ......
  6. mHeader = (window_header_t *) mData;
  7. // Make the window read-only
  8. ssize_t size = memory->size();
  9. mSize = size;
  10. mMaxSize = size;
  11. mFreeOffset = size;
  12. ......
  13. return true;
  14. }

从前面一篇文章Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析中,我们知道,这里得到的IMemory接口,实际上是一个Binder引用,它指向前面在Step 9中创建的MemoryBase对象,当我们第一次调用这个接口的pointer函数时,它便会通过Binder进程间通信机制去请求这个MemoryBase
对象把它内部的匿名共享内存文件描述符返回来给它,而Binder驱动程序发现要传输的是一个文件描述符的时候,就会在目标进程中创建另外一个文件描述
符,这个新建的文件描述符与要传输的文件描述符指向的是同一个文件,在我们这个情景中,这个文件就是我们前面创建的匿名共享内存文件了。因此,在目标进程
中,即在Content
Provider进程中,它可以通过这个新建的文件描述符来访问这块匿名共享内存,这也是匿名共享内存在进程间的共享原理,具体可以参考另外一篇文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

这样,在Content Provider这一侧,就可以把第三方应用程序请求的数据保存在这个匿名共享内存中了,回到前面的Step
14中,下一步要执行的函数便是bulkQuery了,它的作用为请求的数据制定好一个SQL数据库查询计划。这个bulkQuery函数是由一个实现了
IContentProvider接口的Binder对象来实现的,具体可以参考前面一篇文章Android应用程序组件Content Provider的启动过程源代码分析中,这个Binder对象的实际类型是定义在ContentProivder类内部的Transport类。

Step 18. Transport.bulkQuery

这个函数定义在frameworks/base/core/java/android/content/ContentProvider.java文件中:

  1. public abstract class ContentProvider implements ComponentCallbacks {
  2. ......
  3. class Transport extends ContentProviderNative {
  4. ......
  5. public IBulkCursor bulkQuery(Uri uri, String[] projection,
  6. String selection, String[] selectionArgs, String sortOrder,
  7. IContentObserver observer, CursorWindow window) {
  8. ......
  9. Cursor cursor = ContentProvider.this.query(uri, projection,
  10. selection, selectionArgs, sortOrder);
  11. ......
  12. return new CursorToBulkCursorAdaptor(cursor, observer,
  13. ContentProvider.this.getClass().getName(),
  14. hasWritePermission(uri), window);
  15. }
  16. ......
  17. }
  18. ......
  19. }

这个函数主要做了两件事情,一是调用ContentProvider的子类的query函数构造一个数据库查询计划,注意,从这个函数返回来的时候,还
没有真正执行数据库查询的操作,而只是按照查询条件准备好了一个SQL语句,要等到第一次使用的时候才会去执行数据库查询操作;二是使用前面一步得到的
Cursor接口以及传下来的参数window来创建一个CursorToBulkCursorAdaptor对象,这个对象实现了
IBulkCursor接口,同时它也是一个Binder对象,是用来返回给第三方应用程序使用的,第三方应用程序必须通过这个接口来获取从
ContentProvider中查询得到的数据,而这个CursorToBulkCursorAdaptor对象的功能就是利用前面获得的Cursor
接口来执行数据库查询操作,然后把查询得到的结果保存在从参数传下来的window对象内部所引用的匿名共享内存中去。我们先来看
ContentProvider的子类的query函数的实现,在我们这个情景中,这个子类就是ArticlesProvider了,然后再回过头来看看
这个CursorToBulkCursorAdaptor对象是如何把数据库查询计划与匿名共享内存关联起来的。

Step 19. ArticlesProvider.query

这个函数定义在前面一篇文章Android应用程序组件Content Provider应用实例
绍的应用程序ArtilcesProvider源代码工程目录下,在文件packages/experimental/ArticlesProvider
/src/shy/luo/providers/articles/ArticlesProvider.java 中:

  1. public class ArticlesProvider extends ContentProvider {
  2. ......
  3. @Override
  4. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  5. SQLiteDatabase db = dbHelper.getReadableDatabase();
  6. SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
  7. String limit = null;
  8. switch (uriMatcher.match(uri)) {
  9. ......
  10. case Articles.ITEM_POS: {
  11. String pos = uri.getPathSegments().get(1);
  12. sqlBuilder.setTables(DB_TABLE);
  13. sqlBuilder.setProjectionMap(articleProjectionMap);
  14. limit = pos + ", 1";
  15. break;
  16. }
  17. ......
  18. }
  19. Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Articles.DEFAULT_SORT_ORDER : sortOrder, limit);
  20. ......
  21. return cursor;
  22. }
  23. ......
  24. }

从前面的Step
1中可以看到,传进来的参数uri的值为“content://shy.luo.providers.articles/pos”,通过
uriMatcher的match函数来匹配这个uri的时候,得到的匹配码为Articles.ITEM_POS,这个知识点可以参考前面这篇文章Android应用程序组件Content Provider应用实例。因为我们的数据是保存在SQLite数据库里面的,因此,必须要构造一个SQL语句来将所请求的数据查询出来。这里是通过SQLiteQueryBuilder类来构造这个SQL查询语句的,构造好了以后,就调用它的query函数来准备一个数据库查询计划。

Step 20. SQLiteQueryBuilder.query

这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java文件中:

  1. public class SQLiteQueryBuilder
  2. {
  3. ......
  4. public Cursor query(SQLiteDatabase db, String[] projectionIn,
  5. String selection, String[] selectionArgs, String groupBy,
  6. String having, String sortOrder, String limit) {
  7. ......
  8. String sql = buildQuery(
  9. projectionIn, selection, groupBy, having,
  10. sortOrder, limit);
  11. ......
  12. return db.rawQueryWithFactory(
  13. mFactory, sql, selectionArgs,
  14. SQLiteDatabase.findEditTable(mTables));
  15. }
  16. ......
  17. }

这里首先是调用buildQuery函数来构造一个SQL语句,它无非就是根据从参数传来列名子句、select子句、where子句、group
by子句、having子句、order子句以及limit子句来构造一个完整的SQL子句,这些都是SQL语法的基础知识了,这里我们就不关注了。构造
好这个SQL查询语句之后,就调用从参数传下来的数据库对象db的rawQueryWithFactory函数来进一步操作了。

Step 21. SQLiteDatabase.rawQueryWithFactory

这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java文件中:

  1. public class SQLiteDatabase extends SQLiteClosable {
  2. ......
  3. public Cursor rawQueryWithFactory(
  4. CursorFactory cursorFactory, String sql, String[] selectionArgs,
  5. String editTable) {
  6. ......
  7. SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
  8. Cursor cursor = null;
  9. try {
  10. cursor = driver.query(
  11. cursorFactory != null ? cursorFactory : mFactory,
  12. selectionArgs);
  13. } finally {
  14. ......
  15. }
  16. return cursor;
  17. }
  18. ......
  19. }

这个函数会在内部创建一个SQLiteCursorDriver对象driver,然后调用它的query函数来创建一个Cursor对象,这个Cursor对象的实际类型是SQLiteCursor,下面我们将会看到,前面我们也已经看到,这个SQLiteCursor的内部就包含了一个数据库查询计划。

Step 22. SQLiteCursorDriver.query

这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java文件中:

  1. public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
  2. ......
  3. public Cursor query(CursorFactory factory, String[] selectionArgs) {
  4. // Compile the query
  5. SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
  6. try {
  7. ......
  8. // Create the cursor
  9. if (factory == null) {
  10. mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
  11. } else {
  12. mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
  13. }
  14. ......
  15. return mCursor;
  16. } finally {
  17. ......
  18. }
  19. }
  20. ......
  21. }

这里我们就可以清楚地看到,这个函数首先会根据数据库对象mDatabase和原生SQL语句来构造一个SQLiteQuery对象,这个对象的创建的
过程中,就会解析这个原生SQL语句,并且创建好数据库查询计划,这样做的好处是等到真正查询的时候就可以马上从数据库中获得取数据了,而不用去分析和理
解这个SQL字符串语句,这个就是所谓的SQL语句编译了。有了这个SQLiteQuery对象之后,再把它和数据库对象mDatabase等待信息一起
来创建一个SQLiteCursor对象,于是,这个SQLiteCursor对象就圈定要将来要从数据库中获取的数据了。这一步执行完成之后,就把这个SQLiteCursor对象返回给上层,最终回到Step 18中的Transport类bulkQuery函数中。有了这个SQLiteCursor对象之后,就通过创建一个CursorToBulkCursorAdaptor对象来把它和匿名共享内存关联起来,这样,就为将来从数据库中查询得到的数据找到了归宿。

CursorToBulkCursorAdaptor
类定义在frameworks/base/core/java/android/database
/CursorToBulkCursorAdaptor.java文件中,我们来看看它的对象的构造过程,即它的构造函数的实现:

  1. public final class CursorToBulkCursorAdaptor extends BulkCursorNative
  2. implements IBinder.DeathRecipient {
  3. ......
  4. public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName,
  5. boolean allowWrite, CursorWindow window) {
  6. try {
  7. mCursor = (CrossProcessCursor) cursor;
  8. if (mCursor instanceof AbstractWindowedCursor) {
  9. AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
  10. ......
  11. windowedCursor.setWindow(window);
  12. } else {
  13. ......
  14. }
  15. } catch (ClassCastException e) {
  16. ......
  17. }
  18. ......
  19. }
  20. ......
  21. }

这里传进来的参数cursor的类型为SQLiteCursor,从上面的类图我们可以知道,SQLiteCursor
实现了CrossProcessCursor接口,并且继承了AbstractWindowedCursor类,因此,上面第一个if语句的条件成立,于
是就会把这个SQLiteCurosr对象转换为一个AbstractWindowedCursor对象,目的是为了调用它的setWindow函数来把
传进来的CursorWindow对象window保存起来,以便后面用来保存数据。

Step 23. AbstractWindowedCursor.setWindow

这个函数定义在frameworks/base/core/java/android/database/AbstractWindowedCursor.java文件中:

  1. public abstract class AbstractWindowedCursor extends AbstractCursor
  2. {
  3. ......
  4. public void setWindow(CursorWindow window) {
  5. ......
  6. mWindow = window;
  7. }
  8. ......
  9. protected CursorWindow mWindow;
  10. }

这个函数很简单,只是把参数window保存在AbstractWindowedCursor类的成员变量mWindow中。注意,这个成员变量mWindow的访问权限为protected,即AbstractWindowedCursor的子类可以直接访问这个成员变量。

这一步完成以后,就返回到前面的Step 14中去了,执行下面语句:

  1. if (bulkCursor != null) {
  2. reply.writeStrongBinder(bulkCursor.asBinder());
  3. if (wantsCursorMetadata) {
  4. reply.writeInt(bulkCursor.count());
  5. reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
  6. bulkCursor.getColumnNames()));
  7. }
  8. } else {
  9. ......
  10. }

这里的bulkCursor不为null,于是,就会把这个bulkCursor对象写入到数据流reply中,这个接口是要通过Binder进程间通信机制返回到第三方应用程序的,它的实际类型就是我们在前面Step 18中创建的CursorToBulkCursorAdaptor对象了。

从前面的Step
14的分析中,我们知道,这里的布尔变量wantsCursorMetadata为true,于是就会把请求数据的行数以及数据行的ID列索引号一起写入
到数据流reply中去了。这里,我们重点分析IBulkCursor接口的count函数,因为这个调用使得这个Content
Provider会真正去执行数据库查询的操作。至于是如何得到从数据库查询出来的数据行的ID列的位置呢?回忆前面这篇文章Android应用程序组件Content Provider应用实例
我们提到,如果我们想将数据库表中的某一列作为数据行的ID列的话,那么就必须把这个列的名称设置为"_id",这里的
BulkCursorToCursorAdaptor类的静态成员函数findRowIdColumnIndex函数就是根据这个列名"_id"来找到它
是位于数据行的第几列的。

CursorToBulkCursorAdaptor类定义在frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java文件中,它的count成员函数的实现如下所示:

  1. public final class CursorToBulkCursorAdaptor extends BulkCursorNative
  2. implements IBinder.DeathRecipient {
  3. ......
  4. public int count() {
  5. return mCursor.getCount();
  6. }
  7. ......
  8. }

它的成员变量mCursor即为在前面Step 22中创建的SQLiteCursor对象,于是,下面就会执行SQLiteCursor类的getCount成员函数。

Step 24. SQLiteCursor.getCount
      这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java文件中:

  1. public class SQLiteCursor extends AbstractWindowedCursor {
  2. ......
  3. @Override
  4. public int getCount() {
  5. if (mCount == NO_COUNT) {
  6. fillWindow(0);
  7. }
  8. return mCount;
  9. }
  10. ......
  11. }

它里面的成员变量mCount的初始化为NO_COUNT,表示还没有去执行数据库查询操作,因此,还不知道它的值是多少,需要通过调用fillWindow函数来从数据据库中查询中,第三方应用程序所请求的数据一共有多少行。

Step 25. QLiteCursor.fillWindow
      这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java文件中:

  1. public class SQLiteCursor extends AbstractWindowedCursor {
  2. ......
  3. private void fillWindow (int startPos) {
  4. ......
  5. mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
  6. ......
  7. }
  8. ......
  9. }

注意,这里的成员变量mWindow实际上是SQLiteCursor的父类AbstractWindowedCursor的成员变量,是在Step 23中设置的,它的访问权限为protected,因此,SQLiteCursor类可以直接访问它。真正的数据库查询操作是由SQLiteCursor类的成员变量mQuery来执行的,它的类型是SQLiteCursor,是前面的Step 22中创建的,它知道如何去把第三方应用程序请求的数据从数据库中提取出来。

Step 26. SQLiteCursor.fillWindow

这个函数定义在frameworks/base/core/java/android/database/sqlite/SQLiteQuery.java文件中:

  1. public class SQLiteQuery extends SQLiteProgram {
  2. ......
  3. /* package */ int fillWindow(CursorWindow window,
  4. int maxRead, int lastPos) {
  5. ......
  6. try {
  7. ......
  8. try {
  9. ......
  10. // if the start pos is not equal to 0, then most likely window is
  11. // too small for the data set, loading by another thread
  12. // is not safe in this situation. the native code will ignore maxRead
  13. int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
  14. maxRead, lastPos);
  15. ......
  16. return numRows;
  17. } catch (IllegalStateException e){
  18. ......
  19. } catch (SQLiteDatabaseCorruptException e) {
  20. ......
  21. } finally {
  22. ......
  23. }
  24. } finally {
  25. ......
  26. }
  27. }
  28. ......
  29. }

这里我们可以看到,真正的数据库查询操作是由本地方法native_fill_window来执行的,它最终也是调用了sqlite的库函数来执行数据
库查询的操作,这里我们就不跟进去了,对sqlite有兴趣的读者可以自己研究一下。这个函数执行完成之后,就会把从数据库中查询得到的数据的行数返回
来,这个行数最终返回到Step 25中的SQLiteCursor.fillWindow函数,设置在SQLiteCursor类的成员变量mCount中,于是,下次再调用它的getCount函数时,就可以马上返回了。

这一步执行完成之后,就回到前面的Step 14中,最终就把从Content Provider中查询得到的数据通过匿名共享内存返回给第三方应用程序了。

至此,Android应用程序组件Content Provider在应用程序之间共享数据的原理就分析完成了,总的来说,它就是通过Binder进程间通信机制和匿名共享内存来实现的了。

关于应用程序间的数据共享还有另外的一个重要话题,就是数据更新通知机制了。因为数据是在多个应用程序中共享的,当其中一个应用程序改变了这些共享数据
的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。在下一篇文章中,我们将分析Android应用程序组件Content Provider的数据更新通知机制,敬请关注。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

Android应用程序组件Content Provider在应用程序之间共享数据的原理分析的更多相关文章

  1. Android应用程序组件Content Provider的共享数据更新通知机制分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6985171 在Android系统中,应用程序组 ...

  2. Android应用程序组件Content Provider应用实例

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6950440 文简要介绍了Android应用程序 ...

  3. Android应用程序组件Content Provider简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6946067 在Android系统中,Conte ...

  4. Android应用程序组件Content Provider的启动过程源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6963418 通过前面的学习,我们知道在Andr ...

  5. Android四大组件-Content Provider

    http://www.jianshu.com/p/c3ce81b638bd 一.基础回顾. 简介ContentProvider(数据提供者)是在应用程序间共享数据的一种接口机制,虽然我们可以采用文件存 ...

  6. Android学习五:Content Provider 使用

    1ContentProvider相关知识1.1在安卓应用中,通过文件方式对外共享数据,需要进行文件操作读写数据:采用sharedpreferences共享数据,需要使用sharedpreference ...

  7. Android应用安全之Content Provider安全

    android平台提供了Content Provider,将一个应用程序的指定数据集提供给其它应用程序.这些数据可以存储在文件系统.SQLite数据库中,或以任何其它合理的方式存储.其他应用可以通过C ...

  8. Android开发-API指南-Content Provider

    Content Providers 英文原文:http://developer.android.com/guide/topics/providers/content-providers.html 采集 ...

  9. Android开发-API指南-Content Provider基础

    Content Provider Basics 英文原文:http://developer.android.com/guide/topics/providers/content-provider-ba ...

随机推荐

  1. Qwerty78 Trip(组合数,规律,逆元)

    Qwerty78 Trip time limit per test 2 seconds memory limit per test 64 megabytes input standard input ...

  2. Unity 读取CSV与Excel

    前几天看到我们在游戏中需要动态加载某些角色的游戏策划值,关于这个问题怎么解决呢?其实办法很多种,归根到底,就是数据的读取.我们可以想到的存储数据的载体有很多.例如:txt,xml,csv,excel. ...

  3. samba服务器加入域控主机所需要修改的配置文件

    samba服务器加入域控主机,成为域成员,当用户访问samba服务器上的共享文件时,直接到域控主机上进行认证.samba服务器上不需要像先前一样创建系统用户,创建samba用户及密码. 1.安装环境( ...

  4. nodejs报错 events.js:72 throw er; // Unhandled 'error' event

    var http = require('http'); var handlerRequest = function(req,res){ res.end('hello');}; var webServe ...

  5. Volley报错!!!No address associated with hostname

    年轻人检查你的网络去吧,这是没有网络导致的原因

  6. android隐式intent使用场景解析

    Android 隐式intent相信大家都有用过,大部分场景我们用显式intent已经能满足我们的业务需求,隐式intent大部分都是用来启动系统自带的Activity或Service之类的组件.昨天 ...

  7. keycode(来自互联网)

  8. zeromq源码分析笔记之准备(0)

    zeromq这个库主要用于进程通信,包括本地进程.网络通信,涉及到一些基础知识,主要包括管道通信,socket编程的内容,反应器模式(使用IO多路复用实现),无锁队列这几块比较重要的部分,下面的几个链 ...

  9. leetcode_最长公共前缀

    题目:Write a function to find the longest common prefix string amongst an array of strings. 题解:给出的函数为: ...

  10. (原)ubuntu16中编译boost1.61.0库

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5797940.html 参考网址: http://www.boost.org/doc/libs/1_61 ...