生活随笔
收集整理的這篇文章主要介紹了
Android apk动态加载机制的研究
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/singwhatiwanna/article/details/22597587?(來(lái)自singwhatiwanna的csdn博客)
背景
問(wèn)題是這樣的:我們知道,apk必須安裝才能運(yùn)行,如果不安裝要是也能運(yùn)行該多好啊,事實(shí)上,這不是完全不可能的,盡管它比較難實(shí)現(xiàn)。在理論層面上,我們可以通過(guò)一個(gè)宿主程序來(lái)運(yùn)行一些未安裝的apk,當(dāng)然,實(shí)踐層面上也能實(shí)現(xiàn),不過(guò)這對(duì)未安裝的apk有要求。我們的想法是這樣的,首先要明白apk未安裝是不能被直接調(diào)起來(lái)的,但是我們可以采用一個(gè)程序(稱之為宿主程序)去動(dòng)態(tài)加載apk文件并將其放在自己的進(jìn)程中執(zhí)行,本文要介紹的就是這么一種方法,同時(shí)這種方法還有很多問(wèn)題,尤其是資源的訪問(wèn)。因?yàn)閷pk加載到宿主程序中去執(zhí)行,就無(wú)法通過(guò)宿主程序的Context去取到apk中的資源,比如圖片、文本等,這是很好理解的,因?yàn)閍pk已經(jīng)不存在上下文了,它執(zhí)行時(shí)所采用的上下文是宿主程序的上下文,用別人的Context是無(wú)法得到自己的資源的,不過(guò)這個(gè)問(wèn)題貌似可以這么解決:將apk中的資源解壓到某個(gè)目錄,然后通過(guò)文件去操作資源,這只是理論上可行,實(shí)際上還是會(huì)有很多的難點(diǎn)的。除了資源存取的問(wèn)題,還有一個(gè)問(wèn)題是activity的生命周期,因?yàn)閍pk被宿主程序加載執(zhí)行后,它的activity其實(shí)就是一個(gè)普通的類,正常情況下,activity的生命周期是由系統(tǒng)來(lái)管理的,現(xiàn)在被宿主程序接管了以后,如何替代系統(tǒng)對(duì)apk中的activity的生命周期進(jìn)行管理是有難度的,不過(guò)這個(gè)問(wèn)題比資源的訪問(wèn)好解決一些,比如我們可以在宿主程序中模擬activity的生命周期并合適地調(diào)用apk中activity的生命周期方法。本文暫時(shí)不對(duì)這兩個(gè)問(wèn)題進(jìn)行解決,因?yàn)楹茈y,本文僅僅對(duì)apk的動(dòng)態(tài)執(zhí)行機(jī)制進(jìn)行介紹,盡管如此,聽(tīng)起來(lái)還是有點(diǎn)小激動(dòng),不是嗎?
工作原理
如下圖所示,首先宿主程序會(huì)到文件系統(tǒng)比如sd卡去加載apk,然后通過(guò)一個(gè)叫做proxy的activity去執(zhí)行apk中的activity。
關(guān)于動(dòng)態(tài)加載apk,理論上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。
DexClassLoader :可以加載文件系統(tǒng)上的jar、dex、apk
PathClassLoader :可以加載/data/app目錄下的apk,這也意味著,它只能加載已經(jīng)安裝的apk
URLClassLoader :可以加載java中的jar,但是由于dalvik不能直接識(shí)別jar,所以此方法在android中無(wú)法使用,盡管還有這個(gè)類
關(guān)于jar、dex和apk,dex和apk是可以直接加載的,因?yàn)樗鼈兌际腔蛘邇?nèi)部有dex文件,而原始的jar是不行的,必須轉(zhuǎn)換成dalvik所能識(shí)別的字節(jié)碼文件,轉(zhuǎn)換工具可以使用android sdk中platform-tools目錄下的dx
轉(zhuǎn)換命令 :dx --dex --output=dest.jar src.jar
示例
宿主程序的實(shí)現(xiàn)
1. 主界面很簡(jiǎn)單,放了一個(gè)button,點(diǎn)擊就會(huì)調(diào)起apk,我把a(bǔ)pk直接放在了sd卡中,至于先把a(bǔ)pk從網(wǎng)上下載到本地再加載其實(shí)是一個(gè)道理。
[java] view plain
copy @Override??public?void?onClick(View?v)?{??????if?(v?==?mOpenClient)?{??????????Intent?intent?=?new?Intent(this,?ProxyActivity.class);??????????intent.putExtra(ProxyActivity.EXTRA_DEX_PATH,?"/mnt/sdcard/DynamicLoadHost/plugin.apk");??????????startActivity(intent);??????}????}?? 點(diǎn)擊button以后,proxy會(huì)被調(diào)起,然后加載apk并調(diào)起的任務(wù)就交給它了
2. 代理activity的實(shí)現(xiàn)(proxy)
[java] view plain
copy package?com.ryg.dynamicloadhost;????import?java.lang.reflect.Constructor;??import?java.lang.reflect.Method;????import?dalvik.system.DexClassLoader;??import?android.annotation.SuppressLint;??import?android.app.Activity;??import?android.content.pm.PackageInfo;??import?android.os.Bundle;??import?android.util.Log;????public?class?ProxyActivity?extends?Activity?{????????private?static?final?String?TAG?=?"ProxyActivity";????????public?static?final?String?FROM?=?"extra.from";??????public?static?final?int?FROM_EXTERNAL?=?0;??????public?static?final?int?FROM_INTERNAL?=?1;????????public?static?final?String?EXTRA_DEX_PATH?=?"extra.dex.path";??????public?static?final?String?EXTRA_CLASS?=?"extra.class";????????private?String?mClass;??????private?String?mDexPath;????????@Override??????protected?void?onCreate(Bundle?savedInstanceState)?{??????????super.onCreate(savedInstanceState);??????????mDexPath?=?getIntent().getStringExtra(EXTRA_DEX_PATH);??????????mClass?=?getIntent().getStringExtra(EXTRA_CLASS);????????????Log.d(TAG,?"mClass="?+?mClass?+?"?mDexPath="?+?mDexPath);??????????if?(mClass?==?null)?{??????????????launchTargetActivity();??????????}?else?{??????????????launchTargetActivity(mClass);??????????}??????}????????@SuppressLint("NewApi")??????protected?void?launchTargetActivity()?{??????????PackageInfo?packageInfo?=?getPackageManager().getPackageArchiveInfo(??????????????????mDexPath,?1);??????????if?((packageInfo.activities?!=?null)??????????????????&&?(packageInfo.activities.length?>?0))?{??????????????String?activityName?=?packageInfo.activities[0].name;??????????????mClass?=?activityName;??????????????launchTargetActivity(mClass);??????????}??????}????????@SuppressLint("NewApi")??????protected?void?launchTargetActivity(final?String?className)?{??????????Log.d(TAG,?"start?launchTargetActivity,?className="?+?className);??????????File?dexOutputDir?=?this.getDir("dex",?0);??????????final?String?dexOutputPath?=?dexOutputDir.getAbsolutePath();??????????ClassLoader?localClassLoader?=?ClassLoader.getSystemClassLoader();??????????DexClassLoader?dexClassLoader?=?new?DexClassLoader(mDexPath,??????????????????dexOutputPath,?null,?localClassLoader);??????????try?{??????????????Class<?>?localClass?=?dexClassLoader.loadClass(className);??????????????Constructor<?>?localConstructor?=?localClass??????????????????????.getConstructor(new?Class[]?{});??????????????Object?instance?=?localConstructor.newInstance(new?Object[]?{});??????????????Log.d(TAG,?"instance?=?"?+?instance);????????????????Method?setProxy?=?localClass.getMethod("setProxy",??????????????????????new?Class[]?{?Activity.class?});??????????????setProxy.setAccessible(true);??????????????setProxy.invoke(instance,?new?Object[]?{?this?});????????????????Method?onCreate?=?localClass.getDeclaredMethod("onCreate",??????????????????????new?Class[]?{?Bundle.class?});??????????????onCreate.setAccessible(true);??????????????Bundle?bundle?=?new?Bundle();??????????????bundle.putInt(FROM,?FROM_EXTERNAL);??????????????onCreate.invoke(instance,?new?Object[]?{?bundle?});??????????}?catch?(Exception?e)?{??????????????e.printStackTrace();??????????}??????}????}?? 說(shuō)明:程序不難理解,思路是這樣的:采用DexClassLoader去加載apk,然后如果沒(méi)有指定class,就調(diào)起主activity,否則調(diào)起指定的class。activity被調(diào)起的過(guò)程是這樣的:首先通過(guò)類加載器去加載apk中activity的類并創(chuàng)建一個(gè)新對(duì)象,然后通過(guò)反射去調(diào)用這個(gè)對(duì)象的setProxy方法和onCreate方法,setProxy方法的作用是將activity內(nèi)部的執(zhí)行全部交由宿主程序中的proxy(也是一個(gè)activity),onCreate方法是activity的入口,setProxy以后就調(diào)用onCreate方法,這個(gè)時(shí)候activity就被調(diào)起來(lái)了。
待執(zhí)行apk的實(shí)現(xiàn)
1. 為了讓proxy全面接管apk中所有activity的執(zhí)行,需要為activity定義一個(gè)基類BaseActivity,在基類中處理代理相關(guān)的事情,同時(shí)BaseActivity還對(duì)是否使用代理進(jìn)行了判斷,如果不使用代理,那么activity的邏輯仍然按照正常的方式執(zhí)行,也就是說(shuō),這個(gè)apk既可以按照?qǐng)?zhí)行,也可以由宿主程序來(lái)執(zhí)行。
[java] view plain
copy package?com.ryg.dynamicloadclient;????import?android.app.Activity;??import?android.content.Intent;??import?android.os.Bundle;??import?android.util.Log;??import?android.view.View;??import?android.view.ViewGroup.LayoutParams;????public?class?BaseActivity?extends?Activity?{????????private?static?final?String?TAG?=?"Client-BaseActivity";????????public?static?final?String?FROM?=?"extra.from";??????public?static?final?int?FROM_EXTERNAL?=?0;??????public?static?final?int?FROM_INTERNAL?=?1;??????public?static?final?String?EXTRA_DEX_PATH?=?"extra.dex.path";??????public?static?final?String?EXTRA_CLASS?=?"extra.class";????????public?static?final?String?PROXY_VIEW_ACTION?=?"com.ryg.dynamicloadhost.VIEW";??????public?static?final?String?DEX_PATH?=?"/mnt/sdcard/DynamicLoadHost/plugin.apk";????????protected?Activity?mProxyActivity;??????protected?int?mFrom?=?FROM_INTERNAL;????????public?void?setProxy(Activity?proxyActivity)?{??????????Log.d(TAG,?"setProxy:?proxyActivity=?"?+?proxyActivity);??????????mProxyActivity?=?proxyActivity;??????}????????@Override??????protected?void?onCreate(Bundle?savedInstanceState)?{??????????if?(savedInstanceState?!=?null)?{??????????????mFrom?=?savedInstanceState.getInt(FROM,?FROM_INTERNAL);??????????}??????????if?(mFrom?==?FROM_INTERNAL)?{??????????????super.onCreate(savedInstanceState);??????????????mProxyActivity?=?this;??????????}??????????Log.d(TAG,?"onCreate:?from=?"?+?mFrom);??????}????????protected?void?startActivityByProxy(String?className)?{??????????if?(mProxyActivity?==?this)?{??????????????Intent?intent?=?new?Intent();??????????????intent.setClassName(this,?className);??????????????this.startActivity(intent);??????????}?else?{??????????????Intent?intent?=?new?Intent(PROXY_VIEW_ACTION);??????????????intent.putExtra(EXTRA_DEX_PATH,?DEX_PATH);??????????????intent.putExtra(EXTRA_CLASS,?className);??????????????mProxyActivity.startActivity(intent);??????????}??????}????????@Override??????public?void?setContentView(View?view)?{??????????if?(mProxyActivity?==?this)?{??????????????super.setContentView(view);??????????}?else?{??????????????mProxyActivity.setContentView(view);??????????}??????}????????@Override??????public?void?setContentView(View?view,?LayoutParams?params)?{??????????if?(mProxyActivity?==?this)?{??????????????super.setContentView(view,?params);??????????}?else?{??????????????mProxyActivity.setContentView(view,?params);??????????}??????}????????@Deprecated??????@Override??????public?void?setContentView(int?layoutResID)?{??????????if?(mProxyActivity?==?this)?{??????????????super.setContentView(layoutResID);??????????}?else?{??????????????mProxyActivity.setContentView(layoutResID);??????????}??????}????????@Override??????public?void?addContentView(View?view,?LayoutParams?params)?{??????????if?(mProxyActivity?==?this)?{??????????????super.addContentView(view,?params);??????????}?else?{??????????????mProxyActivity.addContentView(view,?params);??????????}??????}??}?? 說(shuō)明:相信大家一看代碼就明白了,其中setProxy方法的作用就是為了讓宿主程序能夠接管自己的執(zhí)行,一旦被接管以后,其所有的執(zhí)行均通過(guò)proxy,且Context也變成了宿主程序的Context,也許這么說(shuō)比較形象:宿主程序其實(shí)就是個(gè)空殼,它只是把其它apk加載到自己的內(nèi)部去執(zhí)行,這也就更能理解為什么資源訪問(wèn)變得很困難,你會(huì)發(fā)現(xiàn)好像訪問(wèn)不到apk中的資源了,的確是這樣的,但是目前我還沒(méi)有很好的方法去解決。
2. 入口activity的實(shí)現(xiàn)
[java] view plain
copy public?class?MainActivity?extends?BaseActivity?{????????private?static?final?String?TAG?=?"Client-MainActivity";????????@Override??????protected?void?onCreate(Bundle?savedInstanceState)?{??????????super.onCreate(savedInstanceState);??????????initView(savedInstanceState);??????}????????private?void?initView(Bundle?savedInstanceState)?{??????????mProxyActivity.setContentView(generateContentView(mProxyActivity));??????}????????private?View?generateContentView(final?Context?context)?{??????????LinearLayout?layout?=?new?LinearLayout(context);??????????layout.setLayoutParams(new?LayoutParams(LayoutParams.MATCH_PARENT,??????????????????LayoutParams.MATCH_PARENT));??????????layout.setBackgroundColor(Color.parseColor("#F79AB5"));??????????Button?button?=?new?Button(context);??????????button.setText("button");??????????layout.addView(button,?LayoutParams.MATCH_PARENT,??????????????????LayoutParams.WRAP_CONTENT);??????????button.setOnClickListener(new?OnClickListener()?{??????????????@Override??????????????public?void?onClick(View?v)?{??????????????????Toast.makeText(context,?"you?clicked?button",??????????????????????????Toast.LENGTH_SHORT).show();??????????????????startActivityByProxy("com.ryg.dynamicloadclient.TestActivity");??????????????}??????????});??????????return?layout;??????}????}?? 說(shuō)明:由于訪問(wèn)不到apk中的資源了,所以界面是代碼寫的,而不是寫在xml中,因?yàn)閤ml讀不到了,這也是個(gè)大問(wèn)題。注意到主界面中有一個(gè)button,點(diǎn)擊后跳到了另一個(gè)activity,這個(gè)時(shí)候是不能直接調(diào)用系統(tǒng)的startActivity方法的,而是必須通過(guò)宿主程序中的proxy來(lái)執(zhí)行,原因很簡(jiǎn)單,首先apk本書(shū)沒(méi)有Context,所以它無(wú)法調(diào)起activity,另外由于這個(gè)子activity是apk中的,通過(guò)宿主程序直接調(diào)用它也是不行的,因?yàn)樗鼘?duì)宿主程序來(lái)說(shuō)是不可見(jiàn)的,所以只能通過(guò)proxy來(lái)調(diào)用,是不是感覺(jué)很麻煩?但是,你還有更好的辦法嗎?
3. 子activity的實(shí)現(xiàn)
[java] view plain
copy package?com.ryg.dynamicloadclient;????import?android.graphics.Color;??import?android.os.Bundle;??import?android.view.ViewGroup.LayoutParams;??import?android.widget.Button;????public?class?TestActivity?extends?BaseActivity{????????@Override??????protected?void?onCreate(Bundle?savedInstanceState)?{??????????super.onCreate(savedInstanceState);??????????Button?button?=?new?Button(mProxyActivity);??????????button.setLayoutParams(new?LayoutParams(LayoutParams.MATCH_PARENT,??????????????????LayoutParams.MATCH_PARENT));??????????button.setBackgroundColor(Color.YELLOW);??????????button.setText("這是測(cè)試頁(yè)面");??????????setContentView(button);??????}????}?? 說(shuō)明:代碼很簡(jiǎn)單,不用介紹了,同理,界面還是用代碼來(lái)寫的。
運(yùn)行效果
1. 首先看apk安裝時(shí)的運(yùn)行效果
2. 再看看未安裝時(shí)被宿主程序執(zhí)行的效果
說(shuō)明:可以發(fā)現(xiàn),安裝和未安裝,執(zhí)行效果是一樣的,差別在于:首先未安裝的時(shí)候由于采用了反射,所以執(zhí)行效率會(huì)略微降低,其次,應(yīng)用的標(biāo)題發(fā)生了改變,也就是說(shuō),盡管apk被執(zhí)行了,但是它畢竟是在宿主程序里面執(zhí)行的,所以它還是屬于宿主程序的,因此apk未安裝被執(zhí)行時(shí)其標(biāo)題不是自己的,不過(guò)這也可以間接證明,apk的確被宿主程序執(zhí)行了,不信看標(biāo)題。最后,我想說(shuō)一下這么做的意義,這樣做有利于實(shí)現(xiàn)模塊化,同時(shí)還可以實(shí)現(xiàn)插件機(jī)制,但是問(wèn)題還是很多的,最復(fù)雜的兩個(gè)問(wèn)題:資源的訪問(wèn)和activity生命周期的管理,期待大家有好的解決辦法,歡迎交流。
代碼下載:
https://github.com/singwhatiwanna/dynamic-load-apk
http://download.csdn.net/detail/singwhatiwanna/7121505
總結(jié)
以上是生活随笔為你收集整理的Android apk动态加载机制的研究的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。