【Android源码 栏目提醒】:网学会员--在 Android源码 编辑为广大网友搜集整理了:Android应用程序组件Content Provider在应用程序之间共享数据的原理分析 - 电子设计绩等信息,祝愿广大网友取得需要的信息,参考学习。
Android应用程序组件Content Provider在应用程序之间共享数据的原理分析 在
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系统匿名共享内存AshmemAnonymous Shared Memory简要介绍和学习计划等一系列文章的学习我们知道在应用程序进程之间以匿名共享内存的方式来传输数据效率是非常高的因为它们之间只需要传递一个文件描述符就可以了。
因此Content Provider组件在不同应用程序之间传输数据正是基于匿名共享内存机制来实现的。
在继续分析Content Provider组件在不同应用程序之间共享数据的原理之前我们假设应用程序之间需要共享的数据是保存在数据库SQLite中的因此接下来的分析都是基于SQLite数据库游标SQLiteCursor来进行的。
SQLiteCursor在共享数据的传输过程中发挥着重要的作用因此我们先来它和其它相关的类的关系图如下所示 首先在第三方应用程序这一侧当它需要访问Content Provider中的数据时它会在本进程中创建一个CursorWindow对象它在内部创建了一块匿名共享内存同时它实现了Parcel接口因此它可以在进程间传输。
接下来第三方应用程序把这个CursorWindow对象连同它内部的匿名共享内存文件描述符通过Binder进程间调用传输到Content Provider这一侧。
这个匿名共享内存文件描述符传输到Binder驱动程序的时候Binder驱动程序就会在目标进程即Content Provider所在的进程中创建另一个匿名共享文件描述符指向前面已经创建好的匿名共享内存因此就实现了在两个进程中共享同一块匿名内存这个过程具体可以参考
Android系统匿名共享内存AshmemAnonymous 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中 java view plaincopy 1. public class ArticlesAdapter 2. ...... 3. 4. private ContentResolver resolver null 5. 6. public ArticlesAdapterContext context 7. resolver context.getContentResolver 8. 9. 10. ...... 11. 12. public Article getArticleByPosint pos 13. Uri uri ContentUris.withAppendedIdArticles.CONTENT_POS_URI pos 14. 15. String projection new String 16. Articles.ID 17. Articles.TITLE 18. Articles.ABSTRACT 19. Articles.URL 20. 21. 22. Cursor cursor resolver.queryuri projection null null Articles.DEFAULT_SORT_ORDER 23. if cursor.moveToFirst 24. return null 25. 26. 27. int id cursor.getInt0 28. String title cursor.getString1 29. String abs cursor.getString2 30. String url cursor.getString3 31. 32. return new Articleid title abs url 33. 34. 这个函数通过应用程序上下文的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文件中 java view plaincopy 1. public abstract class ContentResolver 2. ...... 3. 4. public final Cursor queryUri uri String projection 5. String selection String selectionArgs String sortOrder 6. IContentProvider provider acquireProvideruri 7. if provider null 8. return null 9. 10. try 11. ...... 12. Cursor qCursor provider.queryuri projection selection selectionArgs sortOrder 13. ...... 14. 15. return new CursorWrapperInnerqCursor provider 16. catch RemoteException e 17. ...... 18. catchRuntimeException e 19. ...... 20. 21. 22. 23. ...... 24. 这个函数首先通过调用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文件中 java view plaincopy 1. final class ContentProviderProxy implements IContentProvider 2. ...... 3. 4. public Cursor queryUri url String projection String selection 5. String selectionArgs String sortOrder throws RemoteException 6. //TODO make a pool of windows so we can reuse memory dealers 7. CursorWindow window new CursorWindowfalse / window will be used remotely / 8. BulkCursorToCursorAdaptor adaptor new BulkCursorToCursorAdaptor 9. IBulkCursor bulkCursor bulkQueryInternal 10. url projection selection selectionArgs sortOrder 11. adaptor.getObserver window 12. adaptor 13. if bulkCursor null 14. return null 15. 16. return adaptor 17. 18. 19. ...... 20. 这个函数首先会创建一个CursorWindow对象前面已经说过这个CursorWindow对象包含了一块匿名共享内存它的作用是把这块匿名共享内存通过Binder进程间通信机制传给Content Proivder好让Content Proivder在里面返回所请求的数据。
下面我们就先看看这个CursorWindow对象的创建过程重点关注它是如何在内部创建匿名共享内存的然后再回过头来看看它调用bulkQueryInternal函数来做了些什么事情。
CursorWindow类定义在frameworks/base/core/java/
android/database/CursorWindow.java文件中我们来看看它的构造函数的实现 java view plaincopy 1. public class CursorWindow extends SQLiteClosable implements Parcelable 2. ...... 3. 4. private int nWindow 5. 6. ...... 7. 8. public CursorWindowboolean localWindow 9. ...... 10. 11. native_initlocalWindow 12. 13. 14. ...... 15. 它主要调用本地方法native_init来执行初始化的工作主要就是创建匿名共享内存了传进来的参数localWindow为false表示这个匿名共享内存只能通过远程调用来访问即前面我们所说的通过Content Proivder返回来的Cursor接口来访问这块匿名共享内存里面的数据。
Step 8. CursorWindow.native_init 这是一个JNI方法它对应定义在frameworks/base/core/jni/
android_database_CursorWindow.cpp文件中的native_init_empty函数 cpp view plaincopy 1. static JNINativeMethod sMethods 2. 3. / name signature funcPtr / 4. quotnative_initquot quotZVquot void native_init_empty 5. ...... 6. 函数native_init_empty的定义如下所示 cpp view plaincopy 1. static void native_init_emptyJNIEnv env jobject object jboolean localOnly 2. 3. ...... 4. 5. CursorWindow window 6. 7. window new CursorWindowMAX_WINDOW_SIZE 8. ...... 9. 10. if window-gtinitBufferlocalOnly 11. ...... 12. 13. 14. ...... 15. SET_WINDOWenv object window 16. 这个函数在C层创建了一个CursorWindow对象然后通过调用SET_WINDOW宏来把这个C层的CursorWindow对象与Java层的CursorWindow对象关系起来 cpp view plaincopy 1. define SET_WINDOWenv object window env-gtSetIntFieldobject gWindowField intwindow 这里的gWindowField即定义为Java层的CursorWindow对象中的nWindow成员变量 cpp view plaincopy 1. static jfieldID gWindowField 2. 3. ...... 4. 5. int register_
android_database_CursorWindowJNIEnv env 6. 7. jclass clazz 8. 9. clazz env-gtFindClassquotandroid/database/CursorWindowquot 10. ...... 11. 12. gWindowField env-gtGetFieldIDclazz quotnWindowquot quotIquot 13. 14. ...... 15. 这种用法在
Android应用程序框架层中非常普遍。
下面我们重点关注C层的CursorWindow对象的initBuffer函数的实现。
Step 9. CursorWindow.initBuffer C层的CursorWindow类定义在frameworks/base/core/jni/CursorWindow.cpp文件中 cpp view plaincopy 1. bool CursorWindow::initBufferbool localOnly 2. 3. ...... 4. 5. spltMemoryHeapBasegt heap 6. heap new MemoryHeapBasemMaxSize 0 quotCursorWindowquot 7. if heap NULL 8. mMemory new MemoryBaseheap 0 mMaxSize 9. if mMemory NULL 10. mData uint8_t mMemory-gtpointer 11. if mData 12. mHeader window_header_t mData 13. mSize mMaxSize 14. 15. ...... 16. 17. 18. ...... 19. else 20. ...... 21. 22. 这里我们就可以很清楚地看到在CursorWindow类的内部有一个成员变量mMemory它的类型是MemoryBase。
MemoryBase类为我们封装了匿名共享内存的访问以及在进程间的传输等问题具体可以参考前面一篇文章
Android系统匿名共享内存Anonymous Shared MemoryC调用接口分析这里就不再详述了。
通过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文件中 java view plaincopy 1. final class ContentProviderProxy implements IContentProvider 2. 3. ...... 4. 5. private IBulkCursor bulkQueryInternal 6. Uri url.