編輯:關於Android編程
實現Android動態部署的過程中最重要的是從插件apk中啟動四大組件,經過前面幾篇文章的分析,現在只剩下BroadcastReceiver和ContentProvider了,BroadcastReceiver是可以通過java代碼動態注冊的,可想而知,偷懶一點的辦法就是在解析完AndroidManifest.xml文件後手動注冊一下就好了,這篇文章中會詳細分析一下ContentProvider的安裝流程以及調用getContentResolver方法後的獲取ContentProvider的流程。
在解析完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(); } } } } 注:在實際項目中應用的時候,要注意,在整個應用生命周期中,不要多次調用該方法。
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的安裝流程。
在上一小節中我們看到,在本地成員變量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,需要我們去測試,修復,只有經過大量實際項目的磨練,才能打造出一個合格的框架。
最後,感謝大家的閱讀與支持!
本文結合之前的動態創建fragment來進行一個實踐,來實現用Fragment創建一個選項卡項目布局<LinearLayout xmlns:android=http
Android studio 百度地圖開發(1)配置工程、顯示地圖email:[email protected]最近在學習寫app,需要用到百度地圖,於是整理了
今天來寫一個關於圖片請求的小例子,我們用NetworkImageView這個類來實現,這個類可以直接用在xml控件中,當作imageview,而且內部原理也是使用的Ima
ListView允許用戶通過手指上下滑動的方式將屏幕外的數據滾動到屏幕內,同時屏幕上原有的數據則會滾動出屏幕.1. ListView的簡單用法首先新建一個ListView