日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android apk动态加载机制的研究

發(fā)布時(shí)間:2025/3/15 Android 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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ò),歡迎將生活随笔推薦給好友。