国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android動態加載Activity原理詳解

2019-12-12 06:39:07
字體:
來源:轉載
供稿:網友

activity的啟動流程

加載一個Activity肯定不會像加載一般的類那樣,因為activity作為系統的組件有自己的生命周期,有系統的很多回調控制,所以自定義一個DexClassLoader類加載器來加載插件中的Activity肯定是不可以的。

首先不得不了解一下activity的啟動流程,當然只是簡單的看一下,太詳細的話很難研究清楚。

通過startActivity啟動后,最終通過AMS進行跨進程回調到ApplicationThread的scheduleLaunchActivity,這時會創建一個ActivityClientRecord對象,這個對象表示一個Acticity以及他的相關信息,比如activityInfo字段包括了啟動模式等,還有loadedApk,顧名思義指的是加載過了的APK,他會被放在一個Map中,應用包名到LoadedApk的鍵值對,包含了一個應用的相關信息。然后通過Handler切換到主線程執performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {ActivityInfo aInfo = r.activityInfo;// 1.創建ActivityClientRecord對象時沒有對他的packageInfo賦值,所以它是nullif (r.packageInfo == null) {r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE);}// ...Activity activity = null;try {// 2.非常重要??!這個ClassLoader保存于LoadedApk對象中,它是用來加載我們寫的activity的加載器java.lang.ClassLoader cl = r.packageInfo.getClassLoader();// 3.用加載器來加載activity類,這個會根據不同的intent加載匹配的activityactivity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);if (r.state != null) {r.state.setClassLoader(cl);}} catch (Exception e) {// 4.這里的異常也是非常非常重要的?。。『竺婢透鶕@個提示找到突破口。。。if (!mInstrumentation.onException(activity, e)) {throw new RuntimeException("Unable to instantiate activity " + component+ ": " + e.toString(), e);}}if (activity != null) {Context appContext = createBaseContextForActivity(r, activity);CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());Configuration config = new Configuration(mCompatConfiguration);// 從這里就會執行到我們通??吹降腶ctivity的生命周期的onCreate里面mInstrumentation.callActivityOnCreate(activity, r.state);// 省略的是根據不同的狀態執行生命周期}r.paused = true;mActivities.put(r.token, r);} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {// ...}return activity;}

1.getPackageInfo方法最終返回一個LoadedApk對象,它會從一個HashMap的數據結構中取,mPackages維護了包名和LoadedApk的對應關系,即每一個應用有一個鍵值對對應。如果為null,就新創建一個LoadedApk對象,并將其添加到Map中,重點是這個對象的ClassLoader字段為null!

public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,int flags) {// 為trueboolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;boolean securityViolation = includeCode && ai.uid != 0&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid): true);// ...// includeCode為true// classloader為null?。。eturn getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);}private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {synchronized (mPackages) {WeakReference<loadedapk> ref;if (includeCode) {// includeCode為trueref = mPackages.get(aInfo.packageName);} else {ref = mResourcePackages.get(aInfo.packageName);}LoadedApk packageInfo = ref != null ? ref.get() : null;if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) {if (localLOGV) // ...// packageInfo為null,創建一個LoadedApk,并且添加到mPackages里面packageInfo = new LoadedApk(this, aInfo, compatInfo, this, baseLoader, securityViolation, includeCode &&(aInfo.flags&ApplicationInfo. ) != 0);if (includeCode) {mPackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));} else {mResourcePackages.put(aInfo.packageName, new WeakReference<loadedapk>(packageInfo));}}return packageInfo;}}</loadedapk></loadedapk></loadedapk>

2.獲取這個activity對應的類加載器,由于上面說過,mClassLoader為null,那么就會執行到ApplicationLoaders#getClassLoader(zip, libraryPath, mBaseClassLoader)方法。

public ClassLoader getClassLoader() {synchronized (this) {if (mClassLoader != null) {return mClassLoader;}// ...// 創建加載器,創建默認的加載器// zip為Apk的路徑,libraryPath也就是JNI的路徑mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, libraryPath, mBaseClassLoader);initializeJavaContextClassLoader();StrictMode.setThreadPolicy(oldPolicy);} else {if (mBaseClassLoader == null) {mClassLoader = ClassLoader.getSystemClassLoader();} else {mClassLoader = mBaseClassLoader;}}return mClassLoader;}}

ApplicationLoaders使用單例它的getClassLoader方法根據傳入的zip路徑事實上也就是Apk的路徑來創建加載器,返回的是一個PathClassLoader。并且PathClassLoader只能加載安裝過的APK。這個加載器創建的時候傳入的是當前應用APK的路徑,理所應當的,想加載其他的APK就構造一個傳遞其他APK的類加載器。

3.用該類加載器加載我們要啟動的activity,并反射創建一個activity實例

public Activity newActivity(ClassLoader cl, String className,Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {return (Activity)cl.loadClass(className).newInstance();}

總結一下上面的思路就是,當我們啟動一個activity時,通過系統默認的PathClassLoader來加載這個activity,當然默認情況下只能加載本應用里面的activity,然后就由系統調用到這個activity的生命周期中。

4.這個地方的異常在后面的示例中會出現,到時候分析到原因后就可以找出我們動態加載Activity的思路了。

動態加載Activity:修改系統類加載器

按照這個思路,做這樣的一個示例,按下按鈕,打開插件中的Activity。

插件項目

plugin.dl.pluginactivity

|--MainActivity.java

內容很簡單,就是一個布局上面寫了這是插件中的Activity!并重寫了他的onStart和onDestroy方法。

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 加載到宿主程序中之后,這個R.layout.activity_main就是宿主程序中的R.layout.activity_main了setContentView(R.layout.activity_main);}@Overrideprotected void onStart() {super.onStart();Toast.makeText(this,"onStart", 0).show();}@Overrideprotected void onDestroy() {super.onDestroy();Toast.makeText(this,"onDestroy", 0).show();}}

宿主項目

host.dl.hostactivity

|--MainActivity.java

包括兩個按鈕,第一個按鈕跳轉到插件中的MainActivity.java,第二個按鈕調轉到本應用中的MainActivity.java

private Button btn;private Button btn1;DexClassLoader loader;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);btn1 = (Button) findViewById(R.id.btn1);btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Class activity = null;String dexPath = "/PluginActivity.apk";loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());try {activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");}catch (ClassNotFoundException e) {Log.i("MainActivity", "ClassNotFoundException");}Intent intent = new Intent(MainActivity.this, activity);MainActivity.this.startActivity(intent);}});btn1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(MainActivity.this, MainActivity2.class);MainActivity.this.startActivity(intent);}});

首先我們要將該activity在宿主工程的額AndroidManifest里面注冊。點擊按鈕打開插件中的activity,發現報錯

java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{host.dl.hostactivity/plugin.dl.pluginactivity.MainActivity}: java.lang.ClassNotFoundException: plugin.dl.pluginactivity.MainActivity

#已經使用自定義的加載器,當startActivity時為什么提示找不到插件中的activity?

前面第四點說過這個異常。其實這個異常就是在performLaunchActivity中拋出的,仔細看這個異常打印信息,發現它說plugin.dl.pluginactivity.MainActivity類找不到,可是我們不是剛剛定義了一個DexClassLoader,成功加載了這個類的嗎??怎么這里又提示這個類找不到?

實際上,確實是這樣的,還記得前面說過,系統默認的類加載器PathClassLoader嗎?(因為LoadedApk對象的mClassLoader變量為null,就調用到ApplicationLoaders#getClassLoader方法,即根據當前應用的路徑返回一個默認的PathClassLoader),當執行到mPackages.get(aInfo.packageName);時從Map獲取的LoadedApk中未指定mClassLoader,因此會使用系統默認的類加載器。于是當執行這一句 mInstrumentation.newActivity(cl, component.getClassName(), r.intent);時,由于這個類加載器找不到我們插件工程中的類,因此報錯了。

現在很清楚了,原因就是使用系統默認的這個類加載器不包含插件工程路徑,無法正確加載我們想要的activity造成的。

于是考慮替換系統的類加載器。

private void replaceClassLoader(DexClassLoader loader) {try {Class clazz_Ath = Class.forName("android.app.ActivityThread");Class clazz_LApk = Class.forName("android.app.LoadedApk");Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);Field field1 = clazz_Ath.getDeclaredField("mPackages");field1.setAccessible(true);Map mPackages = (Map) field1.get(currentActivitead);String packageName = MainActivity.this.getPackageName();WeakReference ref = (WeakReference) mPackages.get(packageName);Field field2 = clazz_LApk.getDeclaredField("mClassLoader");field2.setAccessible(true);field2.set(ref.get(), loader);} catch (Exception e) {e.printStackTrace();}}

這段代碼的思路是將ActivityThread類中的mPackages變量中保存的以當前包名為鍵的LoadedApk值的mClassLoader替換成我們自定義的類加載器。當下一次要加載存放在別的地方的插件中的某個Activity時,直接在mPackages變量中能取到,因此用的就是我們修改了的類加載器了。
因此,在打開插件中的activity之前調用replaceClassLoader(loader);方法替換系統的類加載器,就可以了。

效果如下


此時發現可以啟動插件中的activity,因為執行到了他的onStart方法,并且關閉的時候執行了onDestroy方法,但是奇怪的是界面上的控件貌似沒有變化?和啟動他的界面一模一樣,還不能點擊。這是什么原因呢?

顯然,我們只是把插件中的MainActivity類加載過來了,當執行到他的onCreate方法時,在里面調用setContentView使用的布局參數是R.layout.activity_main,當然使用的就是當前應用的資源了!

##已經替換了系統的類加載器為什么加載本應用的activity卻能正常運行?

不過在修正這個問題之前,有沒有發現一個很奇怪的現象,當加載過插件中的activity后,再次啟動本地的activity也是能正常啟動的?這是為什么呢?前面已經替換了默認的類加載器了,并且可以在打開插件中的activity后再點擊第二個按鈕打開本應用的activity之前查看使用的activity,確實是我們已經替換了的類加載器。那這里為什么還能正常啟動本應用的activity呢?玄機就在我們創建DexClassLoader時的第四個參數,父加載器!設置父加載器為當前類的加載器,就能保證類的雙親委派模型不被破壞,在加載類時都是先由父加載器來加載,加載不成功時在由自己加載。不信可以在new這個加載器的時候父加載器的參數設置成其他值,比如系統類加載器,那么當運行activity時肯定會報錯。

接下來解決前面出現的,跳轉到插件activity中界面顯示不對的問題。這個現象出現的原因已經解釋過了,就是因為使用了本地的資源所導致的,因此需要在setContentView時,使用插件中的資源布局。因此在插件Activity中作如下修改

public class MainActivity2 extends Activity {private static View view;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 加載到宿主程序中之后,這個R.layout.activity_main就是宿主程序中的R.layout.activity_main了// setContentView(R.layout.activity_main);if (view != null)setContentView(view);}@Overrideprotected void onStart() {super.onStart();Toast.makeText(this,"onStart", 0).show();}@Overrideprotected void onDestroy() {super.onDestroy();Toast.makeText(this,"onDestroy", 0).show();}private static void setLayout(View v){view = v;}}

然后在宿主Activity中獲取插件資源并將布局填充成View,然后設置給插件中的activity,作為它的ContentView的內容。

Class<!--?--> layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");Field field = layout.getField("activity_main");Integer obj = (Integer) field.get(null);// 使用包含插件APK的Resources對象來獲取這個布局才能正確獲取插件中定義的界面效果//View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);// 或者這樣,但一定要重寫getResources方法,才能這樣寫View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);Method method = activity.getDeclaredMethod("setLayout", View.class);method.setAccessible(true);method.invoke(activity, view);

完整的代碼

public class MainActivity extends Activity {private Resources resources;protected AssetManager assetManager;private Button btn;private Button btn1;DexClassLoader loader;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);btn1 = (Button) findViewById(R.id.btn1);btn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String dexPath = "/PluginActivity.apk";loader = new DexClassLoader(dexPath, MainActivity.this.getApplicationInfo().dataDir, null, getClass().getClassLoader());Class<!--?--> activity = null;Class<!--?--> layout = null;try {activity = loader.loadClass("plugin.dl.pluginactivity.MainActivity");layout = loader.loadClass("plugin.dl.pluginactivity.R$layout");}catch (ClassNotFoundException e) {Log.i("MainActivity", "ClassNotFoundException");}replaceClassLoader(loader);loadRes(dexPath);try {Field field = layout.getField("activity_main");Integer obj = (Integer) field.get(null);// 使用包含插件APK的Resources對象來獲取這個布局才能正確獲取插件中定義的界面效果View view = LayoutInflater.from(MainActivity.this).inflate(resources.getLayout(obj),null);// 或者這樣,但一定要重寫getResources方法,才能這樣寫// View view = LayoutInflater.from(MainActivity.this).inflate(obj, null);Method method = activity.getDeclaredMethod("setLayout", View.class);method.setAccessible(true);method.invoke(activity, view);} catch (Exception e) {e.printStackTrace();}Intent intent = new Intent(MainActivity.this, activity);MainActivity.this.startActivity(intent);}});btn1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(MainActivity.this, MainActivity2.class);MainActivity.this.startActivity(intent);}});}public void loadRes(String path){try {assetManager = AssetManager.class.newInstance();Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);addAssetPath.invoke(assetManager, path);} catch (Exception e) {}resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());// 也可以根據資源獲取主題}private void replaceClassLoader(DexClassLoader loader){try {Class clazz_Ath = Class.forName("android.app.ActivityThread");Class clazz_LApk = Class.forName("android.app.LoadedApk");Object currentActivityThread = clazz_Ath.getMethod("currentActivityThread").invoke(null);Field field1 = clazz_Ath.getDeclaredField("mPackages");field1.setAccessible(true);Map mPackages = (Map)field1.get(currentActivityThread);String packageName = MainActivity.this.getPackageName();WeakReference ref = (WeakReference) mPackages.get(packageName);Field field2 = clazz_LApk.getDeclaredField("mClassLoader");field2.setAccessible(true);field2.set(ref.get(), loader);} catch (Exception e){System.out.println("-------------------------------------" + "click");e.printStackTrace();}}@Overridepublic Resources getResources() {return resources == null ? super.getResources() : resources;}@Overridepublic AssetManager getAssets() {return assetManager == null ? super.getAssets() : assetManager;}}

動態加載Activity:使用代理

還有一種方式啟動插件中的activity的方式就是將插件中的activity當做一個一般的類,不把它當成組件activity,于是在啟動的時候啟動一個代理ProxyActivity,它才是真正的Activity,他的生命周期由系統管理,我們在它里面調用插件Activity里的函數即可。同時,在插件Activity里面保存一個代理Activity的引用,把這個引用當做上下文環境Context理解。

這里插件Activity的生命周期函數均由代理Activity調起,ProxyActivity其實就是一個真正的我們啟動的Activity,而不是啟動插件中的Activity,插件中的“要啟動”的Activity就當做一個很普通的類看待,當成一個包含了一些函數的普通類來理解,只是這個類里面的函數名字起的有些“奇怪”罷了。涉及到訪問資源和更新UI相關的時候通過當前上下文環境,即保存的proxyActivity引用來獲取。

以下面這個Demo為例

宿主項目

com.dl.host

|--MainActivity.java

|--ProxyActivity.java

MainActivity包括一個按鈕,按下按鈕跳轉到插件Activity

public class MainActivity extends Activity{private Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button)findViewById(R.id.btn);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {MainActivity.this.startActivity(new Intent(MainActivity.this, ProxyActivity.class));}});}}

ProxyActivity就是我們要啟動的插件Activity的一個傀儡,代理。是系統維護的Activity。

public class ProxyActivity extends Activity{private DexClassLoader loader;private Activity activity;private Class<!--?--> clazz = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);loader = new DexClassLoader("/Plugin.apk", getApplicationInfo().dataDir, null, getClass().getClassLoader());try {clazz = loader.loadClass("com.dl.plugin.MainActivity");} catch (ClassNotFoundException e) {e.printStackTrace();}// 設置插件activity的代理try {Method setProxy = clazz.getDeclaredMethod("setProxy", Activity.class);setProxy.setAccessible(true);activity = (Activity)clazz.newInstance();setProxy.invoke(activity, this);Method onCreate = clazz.getDeclaredMethod("onCreate", Bundle.class);onCreate.setAccessible(true);onCreate.invoke(activity, savedInstanceState);} catch (Exception e) {e.printStackTrace();}}@Overrideprotected void onStart() {super.onStart();// 調用插件activity的onStart方法Method onStart = null;try {onStart = clazz.getDeclaredMethod("onStart");onStart.setAccessible(true);onStart.invoke(activity);} catch (Exception e) {e.printStackTrace();}}@Overrideprotected void onDestroy() {super.onStart();// 調用插件activity的onDestroy方法Method onDestroy = null;try {onDestroy = clazz.getDeclaredMethod("onDestroy");onDestroy.setAccessible(true);onDestroy.invoke(activity);} catch (Exception e) {e.printStackTrace();}}}

可以看到,ProxyActivity其實就是一個真正的Activity,我們啟動的就是這個Activity,而不是插件中的Activity。

插件項目

com.dl.plugin

|--MainActivity.java

保存了一個代理Activity的引用,值得注意的是,由于訪問插件中的資源需要額外的操作,要加載資源,因此這里未使用插件項目里面的資源,所以我使用代碼添加的TextView,但原理和前面講的內容是一樣的。

public class MainActivity extends Activity {private Activity proxyActivity;public void setProxy(Activity proxyActivity) { this.proxyActivity = proxyActivity;}// 里面的所有操作都由代理activity來操作@Overrideprotected void onCreate(Bundle savedInstanceState) {TextView tv = new TextView(proxyActivity);tv.setText("插件Activity");proxyActivity.setContentView(tv,new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));}@Overrideprotected void onStart() {Toast.makeText(proxyActivity, "插件onStart", 0).show();}@Overrideprotected void onDestroy() {Toast.makeText(proxyActivity, "插件onDestroy", 0).show();}}

這種方法相比較前面修改系統加載器的方法需要自己維護生命周期,比較麻煩,前一種方式由系統自己維護,并且啟動的就是插件中實實在在的Activity。

前一種方式要在宿主的AndroidManifest里面聲明插件Activity,這樣當activity太多時就要聲明很多,比較繁瑣,不過也可以不聲明逃過系統檢查。后面這種方式就只需要一個代理ProxyActivity類即可。在他的onCreate里面根據傳遞的值選擇加載插件中的哪個Activity即可。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 安多县| 谢通门县| 南开区| 鸡东县| 榆树市| 夹江县| 星子县| 米泉市| 大同县| 甘南县| 开平市| 图木舒克市| 万全县| 赣榆县| 丹巴县| 渭源县| 全州县| 丽江市| 东兰县| 荆门市| 建阳市| 宁武县| 仙居县| 崇州市| 珲春市| 马边| 连江县| 塔城市| 冕宁县| 镇远县| 交口县| 杨浦区| 黄山市| 莱西市| 万荣县| 郧西县| 那曲县| 南投市| 德清县| 黑河市| 宁阳县|