Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android動態部署六:如何從插件apk中啟動BroadcastReceiver和ContentProvider

Android動態部署六:如何從插件apk中啟動BroadcastReceiver和ContentProvider

日期:2017/2/23 15:39:17      編輯:關於Android編程

實現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);

相信大部分的讀者都知道,在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方法:

@Override
protected 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 generateApplicationProvidersLocked(ProcessRecord app) {
    List providers = null;
    try {
        ParceledListSlice 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 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

ActivityThread.java

private void handleBindApplication(AppBindData data) {
    ...
    List 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 providers) {
    final ArrayList results =
        new ArrayList();

    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 providers) {

在解析完插件apk的manifest文件之後,我們可以調用installContentProviders方法安裝插件中的ContentProvider:
DynamicActivityThread.java

public synchronized void installContentProviders(List providers) {
    try {
        mActivityThreadReflect.setMethod("installContentProviders", Context.class, List.class)
                .invoke(currentActivityThread(), getInitialApplication(),
                        generateProviderInfos(providers));
    } catch (Exception e) {
    }
}

private List generateProviderInfos(List providers) {
    List 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,需要我們去測試,修復,只有經過大量實際項目的磨練,才能打造出一個合格的框架。

最後,感謝大家的閱讀與支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved