转载请注明出处:http://blog.csdn.net/ximsfei/article/details/51083464
github地址:https://github.com/ximsfei/DynamicDeploymentApk
实现Android动态部署的过程中最重要的是从插件apk中启动四大组件,经过前面几篇文章的分析,现在只剩下BroadcastReceiver和ContentProvider了,BroadcastReceiver是可以通过java代码动态注册的,可想而知,偷懒一点的办法就是在解析完AndroidManifest.xml文件后手动注册一下就好了,这篇文章中会详细分析一下ContentProvider的安装流程以及调用getContentResolver方法后的获取ContentProvider的流程。
动态注册BroadcastReceiver
在解析完AndroidManifest.xml之后可以调用如下代码动态注册:
private void registerStaticBroadcastReceiver(DynamicApkInfo info) { int N = info.receivers.size(); for (int i = 0; i < N; i++) { int M = info.receivers.get(i).intents.size(); for (int j = 0; j < M; j++) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(info.receivers.get(i).intents.get(j).getAction(0)); try { mApplicationContext.registerReceiver((BroadcastReceiver) info.classLoader .loadClass(info.receivers.get(i).info.name).newInstance(), intentFilter); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }}
注:在实际项目中应用的时候,要注意,在整个应用生命周期中,不要多次调用该方法。
ContentProvider使用
Uri uri = Uri.parse("content://dynamic/content/1");getContentResolver().query(uri, null, null, null, null);
<provider android:name=".PluginContentProvider" android:authorities="dynamic" android:enabled="true" android:exported="true" ></provider>
相信大部分的读者都知道,在Android中通过上面简单的两行代码就可以调用注册在manifest文件中的PluginContentProvider的query方法,接下来我们先分析一下,调用getContentResolver().query()方法之后,源码的执行流程,下图就是调用该方法后的时序图:
首先会从ContextImpl中获取ContextImpl$ApplicationContentResolver对象, 该类继承自ContentResolver,并且在ContextImpl构造方法中创建:
private static final class ApplicationContentResolver extends ContentResolver {}private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, Display display, Configuration overrideConfiguration, int createDisplayWithId) { ... mContentResolver = new ApplicationContentResolver(this, mainThread, user);}
在ContentResolver的query方法中会调用ContextImpl$ApplicationContentResolver类重写的acquireUnstableProvider方法,并且最终会调用ActivityThread中的acquireProvider方法:
@Overrideprotected IContentProvider acquireUnstableProvider(Context c, String auth) { return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), false);}
ActivityThread.java
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);//如果在mProviderMap中存在,则返回 if (provider != null) { return provider; } // There is a possible race here. Another thread may try to acquire // the same provider at the same time. When this happens, we want to ensure // that the first one wins. // Note that we cannot hold the lock while acquiring and installing the // provider since it might take a long time to run and it could also potentially // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; try { //通过ActivityManagerService查询ContentProvider,存在则安装 holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; } // Install provider will increment the reference count for us, and break // any ties in the race. holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider;}public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { synchronized (mProviderMap) { final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; } IContentProvider provider = pr.mProvider; IBinder jBinder = provider.asBinder(); if (!jBinder.isBinderAlive()) { // The hosting process of the provider has died; we can't // use this one. Log.i(TAG, "Acquiring provider " + auth + " for user " + userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; } // Only increment the ref count if we have one. If we don't then the // provider is not reference counted and never needs to be released. ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { incProviderRefLocked(prc, stable); } return provider; }}private IActivityManager.ContentProviderHolder installProvider(Context context, IActivityManager.ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) { ContentProvider localProvider = null; IContentProvider provider; if (holder == null || holder.provider == null) { if (DEBUG_PROVIDER || noisy) { Slog.d(TAG, "Loading provider " + info.authority + ": " + info.name); } Context c = null; ApplicationInfo ai = info.applicationInfo; if (context.getPackageName().equals(ai.packageName)) { c = context; } else if (mInitialApplication != null && mInitialApplication.getPackageName().equals(ai.packageName)) { c = mInitialApplication; } else { try { c = context.createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE); } catch (PackageManager.NameNotFoundException e) { // Ignore } } if (c == null) { Slog.w(TAG, "Unable to get context for package " + ai.packageName + " while loading content provider " + info.name); return null; } try { final java.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl. loadClass(info.name).newInstance(); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + info.name + " from sourceDir " + info.applicationInfo.sourceDir); return null; } if (DEBUG_PROVIDER) Slog.v( TAG, "Instantiating local provider " + info.name); // XXX Need to create the correct context for this provider. localProvider.attachInfo(c, info); } catch (java.lang.Exception e) { if (!mInstrumentation.onException(null, e)) { throw new RuntimeException( "Unable to get provider " + info.name + ": " + e.toString(), e); } return null; } } else { provider = holder.provider; if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": " + info.name); } IActivityManager.ContentProviderHolder retHolder; synchronized (mProviderMap) { if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider + " / " + info.name); IBinder jBinder = provider.asBinder(); if (localProvider != null) { ComponentName cname = new ComponentName(info.packageName, info.name); ProviderClientRecord pr = mLocalProvidersByName.get(cname); if (pr != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, " + "using existing local provider"); } provider = pr.mProvider; } else { holder = new IActivityManager.ContentProviderHolder(info); holder.provider = provider; holder.noReleaseNeeded = true; pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); mLocalProvidersByName.put(cname, pr); } retHolder = pr.mHolder; } else { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } // We need to transfer our new reference to the existing // ref count, releasing the old one... but only if // release is needed (that is, it is not running in the // system process). if (!noReleaseNeeded) { incProviderRefLocked(prc, stable); try { ActivityManagerNative.getDefault().removeContentProvider( holder.connection, stable); } catch (RemoteException e) { //do nothing content provider object is dead any way } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked( provider, localProvider, holder); if (noReleaseNeeded) { prc = new ProviderRefCount(holder, client, 1000, 1000); } else { prc = stable ? new ProviderRefCount(holder, client, 1, 0) : new ProviderRefCount(holder, client, 0, 1); } mProviderRefCountMap.put(jBinder, prc); } retHolder = prc.holder; } } return retHolder;}
在acquireProvider方法中先会调用acquireExistingProvider方法,检测我们所需要的ContentProvider是否在本地变量mProviderMap中,如果存在,并不为null,则直接返回;否则会通过ActivityManagerService查询该ContentProvider是否存在,如果存在,则安装,否则返回null。看到这,我似乎想到了一点,我们是否可以在解析完manifest文件后,然后调用installProvider方法将ContentProvider安装到mProviderMap中呢?带着这样的疑问,我们接着来看应用启动后ContentProvider的安装流程。
ContentProvider安装流程
在上一小节中我们看到,在本地成员变量mProviderMap中不存在的ContentProvider,会通过ActivityManagerService去查询android:authorities对应的ContentProvider,我想这应该是去查询其他应用的Provider吧,当前应用的Provider应该在应用启动时就已经cache到本地变量mProviderMap中了,带这样的猜想,又重新去阅读了一下Apk的启动流程,大家都知道一个应用启动后最先调用的是ActivityThread的main方法,那就从main方法开始,来看看ContentProvider安装流程的时序图:
照着源码一直看下去
main->attach->attachApplication->generateApplicationProvidersLocked->queryContentProviders->bindApplication->handleBindApplication->installContentProviders->installProvider->publishContentProviders
我发现,先前的猜想是对的,在应用启动后会通过AMS,PMS查询本应用中的ContentProviders,查询结果会封装到List中,并且会在ActivityThread调用installContentProviders安装所有本应用的ContentProvider,安装完成后调用AMS的publishContentProviders方法,将ContentProvider publish给其他应用。
AMS.java:
private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) { List<ProviderInfo> providers = null; try { ParceledListSlice<ProviderInfo> slice = AppGlobals.getPackageManager(). queryContentProviders(app.processName, app.uid, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); providers = slice != null ? slice.getList() : null; } catch (RemoteException ex) { } if (DEBUG_MU) Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid); int userId = app.userId; if (providers != null) { int N = providers.size(); app.pubProviders.ensureCapacity(N + app.pubProviders.size()); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags); if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_OWNER) { // This is a singleton provider, but a user besides the // default user is asking to initialize a process it runs // in... well, no, it doesn't actually run in this process, // it runs in the process of the default user. Get rid of it. providers.remove(i); N--; i--; continue; } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton); mProviderMap.putProviderByClass(comp, cpr); } if (DEBUG_MU) Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); if (!cpi.multiprocess || !"android".equals(cpi.packageName)) { // Don't add this if it is a platform component that is marked // to run in multiple processes, because this is actually // part of the framework so doesn't make sense to track as a // separate apk in the process. app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode, mProcessStats); } ensurePackageDexOpt(cpi.applicationInfo.packageName); } } return providers;}public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) { if (providers == null) { return; } enforceNotIsolatedCaller("publishContentProviders"); synchronized (this) { final ProcessRecord r = getRecordForAppLocked(caller); if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller + " (pid=" + Binder.getCallingPid() + ") when publishing content providers"); } final long origId = Binder.clearCallingIdentity(); final int N = providers.size(); for (int i=0; i<N; i++) { ContentProviderHolder src = providers.get(i); if (src == null || src.info == null || src.provider == null) { continue; } ContentProviderRecord dst = r.pubProviders.get(src.info.name); if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name); mProviderMap.putProviderByClass(comp, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { mProviderMap.putProviderByName(names[j], dst); } int NL = mLaunchingProviders.size(); int j; for (j=0; j<NL; j++) { if (mLaunchingProviders.get(j) == dst) { mLaunchingProviders.remove(j); j--; NL--; } } synchronized (dst) { dst.provider = src.provider; dst.proc = r; dst.notifyAll(); } updateOomAdjLocked(r); maybeUpdateProviderUsageStatsLocked(r, src.info.packageName, src.info.authority); } } Binder.restoreCallingIdentity(origId); }}
ActivityThread.java
private void handleBindApplication(AppBindData data) { ... List<ProviderInfo> providers = data.providers; if (providers != null) { installContentProviders(app, providers); // For process that contains content providers, we want to // ensure that the JIT is enabled "at some point". mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); } ...}private void installContentProviders( Context context, List<ProviderInfo> providers) { final ArrayList<IActivityManager.ContentProviderHolder> results = new ArrayList<IActivityManager.ContentProviderHolder>(); for (ProviderInfo cpi : providers) { if (DEBUG_PROVIDER) { StringBuilder buf = new StringBuilder(128); buf.append("Pub "); buf.append(cpi.authority); buf.append(": "); buf.append(cpi.name); Log.i(TAG, buf.toString()); } IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } try { ActivityManagerNative.getDefault().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { }}
等等,看到这,我发现installContentProviders方法的参数很眼熟,ProviderInfo这不就是我们从AndroidManifest中解析出来的数据么,看来在前面一篇文章Android动态部署二:APK安装及AndroidManifest.xml解析流程分析中,我的推荐是对的,通过移植源码中解析AndroidManifest的代码,这样解析较为充分,并且解析出来的数据结构,可以减少我们很多的工作量。
private void installContentProviders( Context context, List<ProviderInfo> providers) {
在解析完插件apk的manifest文件之后,我们可以调用installContentProviders方法安装插件中的ContentProvider:
DynamicActivityThread.java
public synchronized void installContentProviders(List<DynamicApkParser.Provider> providers) { try { mActivityThreadReflect.setMethod("installContentProviders", Context.class, List.class) .invoke(currentActivityThread(), getInitialApplication(), generateProviderInfos(providers)); } catch (Exception e) { }}private List<ProviderInfo> generateProviderInfos(List<DynamicApkParser.Provider> providers) { List<ProviderInfo> providerInfos = new ArrayList<>(); for (DynamicApkParser.Provider p : providers) { p.info.packageName = getHostPackageName(); p.info.applicationInfo.packageName = getHostPackageName(); providerInfos.add(p.info); } return providerInfos;}
至此,我们已经可以在Android开发中,通过非代理模式实现真正意义上的插件化了,无需修改任何插件apk代码,指定插件apk路径即可启动。当然这里还存在很多bug,需要我们去测试,修复,只有经过大量实际项目的磨练,才能打造出一个合格的框架。
最后,感谢大家的阅读与支持!