当前位置: 代码迷 >> Android >> Android动态部署六:怎么从插件apk中启动BroadcastReceiver和ContentProvider
  详细解决方案

Android动态部署六:怎么从插件apk中启动BroadcastReceiver和ContentProvider

热度:482   发布时间:2016-04-24 11:14:07.0
Android动态部署六:如何从插件apk中启动BroadcastReceiver和ContentProvider

转载请注明出处: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,需要我们去测试,修复,只有经过大量实际项目的磨练,才能打造出一个合格的框架。

最后,感谢大家的阅读与支持!

  相关解决方案