目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案;那么对于ContentProvider,它又有什么特点?应该如何实现它的插件化?
与Activity,BroadcastReceiver等频繁被使用的组件不同,我们接触和使用ContentProvider的机会要少得多;但是,ContentProvider这个组件对于Android系统有着特别重要的作用——作为一种极其方便的数据共享的手段,ContentProvider使得广大第三方App能够在壁垒森严的系统中自由呼吸。
在Android系统中,每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋予给自己所属的用户,这就限制了应用程序之间相互读写数据的操作。应用程序之间如果希望能够进行交互,只能采取跨进程通信的方式;Binder机制能够满足一般的IPC需求,但是如果应用程序之间需要共享大量数据,单纯使用Binder是很难办到的——我相信大家对于Binder 1M缓冲区以及TransactionTooLargeException一定不陌生;ContentProvider使用了匿名共享内存(Ashmem)机制完成数据共享,因此它可以很方便地完成大量数据的传输。Android系统的短信,联系人,相册,媒体库等等一系列的基础功能都依赖与ContentProvider,它的重要性可见一斑。
既然ContentProvider的核心特性是数据共享,那么要实现它的插件化,必须能让插件能够把它的ContentProvider共享给系统——如果不能「provide content」那还叫什么ContentProvider?
但是,如果回想一下Activity等组件的插件化方式,在涉及到「共享」这个问题上,一直没有较好的解决方案:
- 系统中的第三方App无法启动插件中带有特定IntentFilter的Activity,因为系统压根儿感受不到插件中这个真正的Activity的存在。
- 插件中的静态注册的广播并不真正是静态的,而是使用动态注册广播模拟实现的;这就导致如果宿主程序进程死亡,这个静态广播不会起作用;这个问题的根本原因在由于BroadcastReceiver的IntentFilter的不可预知性,使得我们没有办法把静态广播真正“共享”给系统。
- 我们没有办法在第三方App中启动或者绑定插件中的Service组件;因为插件的Service并不是真正的Service组件,系统能感知到的只是那个代理Service;因此如果插件如果带有远程Service组件,它根本不能给第三方App提供远程服务。
虽然在插件系统中一派生机勃勃的景象,Activity,Service等插件组件百花齐放,插件与宿主、插件与插件争奇斗艳;但是一旦脱离了插件系统的温室,这一片和谐景象不复存在:插件组件不过是傀儡而已;活着的,只有宿主——整个插件系统就是一座死寂的鬼城,各个插件组件借尸还魂般地依附在宿主身上,了无生机。
既然希望把插件的ContentProvider共享给整个系统,让第三方的App都能获取到我们插件共享的数据,我们必须解决这个问题;下文将会围绕这个目标展开,完成ContentProvider的插件化,并且顺带给出上述问题的解决方案。阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的 contentprovider-management 模块。另外,插件框架原理解析系列文章见 索引。
ContentProvider工作原理
首先我们还是得分析一下ContentProvider的工作原理,很多插件化的思路,以及一些Hook点的发现都严重依赖于对于系统工作原理的理解;对于ContentProvider的插件化,这一点特别重要。
铺垫工作
如同我们通过startActivity
来启动Activity一样,与ContentProvider打交道的过程也是从Context类的一个方法开始的,这个方法叫做getContentResolver
,使用ContentProvider的典型代码如下:
1 2 |
ContentResolver resolver = content.getContentResolver(); resolver.query(Uri.parse("content://authority/test"), null, null, null, null); |
直接去ContextImpl类里面查找的getContentResolver
实现,发现这个方法返回的类型是android.app.ContextImpl.ApplicationContentResolver,这个类是抽象类android.content.ContentResolver的子类,resolver.query
实际上是调用父类ContentResolver的query
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) { Preconditions.checkNotNull(uri, "uri"); IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { return null; } IContentProvider stableProvider = null; Cursor qCursor = null; try { long startTime = SystemClock.uptimeMillis(); ICancellationSignal remoteCancellationSignal = null; if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); remoteCancellationSignal = unstableProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } try { qCursor = unstableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } catch (DeadObjectException e) { // The remote process has died... but we only hold an unstable // reference though, so we might recover!!! Let's try!!!! // This is exciting!!1!!1!!!!1 unstableProviderDied(unstableProvider); stableProvider = acquireProvider(uri); if (stableProvider == null) { return null; } qCursor = stableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } // 略... } |
注意这里面的那个try..catch
语句,query
方法首先尝试调用抽象方法acquireUnstableProvider拿到一个IContentProvider对象,并尝试调用这个”unstable”对象的query
方法,万一调用失败(抛出DeadObjectExceptopn,熟悉Binder的应该了解这个异常)说明ContentProvider所在的进程已经死亡,这时候会尝试调用acquireProvider
这个抽象方法来获取一个可用的IContentProvider(代码里面那个萌萌的注释说明了一切^_^);由于这两个acquire*
都是抽象方法,我们可以直接看子类ApplicationContentResolver
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override protected IContentProvider acquireUnstableProvider(Context c, String auth) { return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(authProvider(c, ContentProvider.getAuthorityWithoutUserId(authڎContentProvider,它又有什么特点?应该如何实现它的插件化?
与Activity,BroadcastReceiver等频繁被使用的组件不同,我们接触和使用ContentProvider的机会要少得多;但是,ContentProvider这个组件对于Android系统有着特别重要的作用——作为一种极其方便的数据共享的手段,ContentProvider使得广大第三方App能够在壁垒森严的系统中自由呼吸。 在Android系统中,每一个应用程序都有自己的用户ID,而每一个应用程序所创建的文件的读写权限都是只赋予给自己所属的用户,这就限制了应用程序之间相互读写数据的操作。应用程序之间如果希望能够进行交互,只能采取跨进程通信的方式;Binder机制能够满足一般的IPC需求,但是如果应用程序之间需要共享大量数据,单纯使用Binder是很难办到的——我相信大家对于Binder 1M缓冲区以及TransactionTooLargeException一定不陌生;ContentProvider使用了匿名共享内存(Ashmem)机制完成数据共享,因此它可以很方便地完成大量数据的传输。Android系统的短信,联系人,相册,媒体库等等一系列的基础功能都依赖与ContentProvider,它的重要性可见一斑。 既然ContentProvider的核心特性是数据共享,那么要实现它的插件化,必须能让插件能够把它的ContentProvider共享给系统——如果不能「provide content」那还叫什么ContentProvider? 但是,如果回想一下Activity等组件的插件化方式,在涉及到「共享」这个问题上,一直没有较好的解决方案:
虽然在插件系统中一派生机勃勃的景象,Activity,Service等插件组件百花齐放,插件与宿主、插件与插件争奇斗艳;但是一旦脱离了插件系统的温室,这一片和谐景象不复存在:插件组件不过是傀儡而已;活着的,只有宿主——整个插件系统就是一座死寂的鬼城,各个插件组件借尸还魂般地依附在宿主身上,了无生机。 既然希望把插件的ContentProvider共享给整个系统,让第三方的App都能获取到我们插件共享的数据,我们必须解决这个问题;下文将会围绕这个目标展开,完成ContentProvider的插件化,并且顺带给出上述问题的解决方案。阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的 contentprovider-management 模块。另外,插件框架原理解析系列文章见 索引。 ContentProvider工作原理首先我们还是得分析一下ContentProvider的工作原理,很多插件化的思路,以及一些Hook点的发现都严重依赖于对于系统工作原理的理解;对于ContentProvider的插件化,这一点特别重要。 铺垫工作如同我们通过
直接去ContextImpl类里面查找的
注意这里面的那个
|