前言:
在目前的軟硬件環境下,Native App與Web App在用戶體驗上有着明顯的優勢,但在實際項目中有些會因爲業務的頻繁變更而頻繁的升級客戶端,造成較差的用戶體驗,而這也恰恰是Web App的優勢。現如今很多項目要求需要採用類似於微信或Q遊這樣的插件化開發模式越來越多,本文就是闡述android的動態加載技術來滿足插件化開發模式的文章。
1.基本概念
1.1 在Android中可以動態加載,但無法像Java中那樣方便動態加載jar。
Android的虛擬機(DalvikVM)是不認識Java打出jar的byte code,需要通過dx工具來優化轉換成Dalvikbyte code才行。這一點在咱們Android項目打包的apk中可以看出:引入其他Jar的內容都被打包進了classes.dex。即android要加載的java類必須dex格式的代碼文件.
1.2 在Android中可以加載基於NDK的so庫。
NDK的執行效率很高,加密性很好,但同時開發入門難度大,一般用於加解密、數學運算等場合。so的加載很簡單,如果APK發佈時已經攜帶了so文件,只需要在加載時調用System.loadLibrary(libName)方法即可。由於軟件的安裝目錄中存放so的目錄是沒有寫權限的,開發者不能更改該目錄的內容,所以如果要動態加載存放在其他地方的so文件,用System.load(pathName)方法即可。
1.3 在Android中支持動態加載dex文件的兩種方式:
DexClassLoader:這個可以加載jar/apk/dex,也可以從SD卡中加載,也是本文的重點
PathClassLoader:只能加載已經安裝到Android系統中的apk文件。也就是 /data/app 目錄下的 apk 文件。其它位置的文件加載的時候都會出現 ClassNotFoundException.因爲 PathClassLoader 會去讀取 /data/dalvik-cache 目錄下的經過 Dalvik 優化過的 dex 文件,這個目錄的 dex 文件是在安裝 apk 包的時候由 Dalvik 生成的。
2.注意
2.1 採用不用安裝的插件開發模式,只能夠使用 DexClassLoader進行加載.不過動態加載是有一些限制的,比如插件(子apk)包中的Activity、Service類是不能動態加載的,因爲缺少聲明;即使你在Manifest文件中進行了聲明,系統默認也是到安裝apk所在的路徑中去尋找類,所以你會遇到一個ClassNotFound的異常。插件裏你可以用主apk中先前放入的layout、strings等資源。但是插件中自帶的界面只能用純代碼進行編寫,插件中是不能加載插件(子apk)中的xml作爲layout等資源使用的。所以在開發上一些特效會比較困難些,建議預先植入主apk中。
2.2 大家可以看看DexClassLoader的API文檔,裏面不提倡從SD卡加載,不安全
3.如何製作插件
3.1 把工程導出爲jar包
3.2 執行SDK安裝目錄android-sdk-windows\platform-tools下的dx命令,把jar包轉換爲dex格式
dx --dex --output=dex名
jar包名
4.如何做到啓動未安裝的apk中的activity?
採用反射機制,把主apk中的activity的context傳遞到插件的activity中,然後採用反射進行回調插件activity的方法。不足之出就是,插件中的activity並不是真正的activity,它只是運行在主activity中。比如:點擊返回直接退出當前activity而不是回到主activity。實例如下:
這是調用的Activity:
- package com.beyondsoft.activity;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import dalvik.system.DexClassLoader;
- import android.app.Activity;
- import android.content.pm.PackageInfo;
- import android.os.Bundle;
- import android.util.Log;
- public class PlugActivity extends Activity {
- private Class mActivityClass;
- private Object mActivityInstance;
- Class localClass;
- private Object instance;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Bundle paramBundle = new Bundle();
- paramBundle.putBoolean("KEY_START_FROM_OTHER_ACTIVITY", true);
- paramBundle.putString("str", "PlugActivity");
- String dexpath = "/sdcard/FragmentProject.apk";
- String dexoutputpath = "/mnt/sdcard/";
- LoadAPK(paramBundle, dexpath, dexoutputpath);
- }
- @Override
- protected void onStart() {
- super.onStart();
- Method start;
- try {
- start = localClass.getMethod("onStart");
- start.invoke(instance);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- @Override
- protected void onResume() {
- // TODO Auto-generated method stub
- super.onResume();
- Method resume;
- try {
- resume = localClass.getMethod("onResume");
- resume.invoke(instance);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- @Override
- protected void onPause() {
- super.onPause();
- Method pause;
- try {
- pause = localClass.getMethod("onPause");
- pause.invoke(instance);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- @Override
- protected void onStop() {
- super.onStop();
- try {
- Method stop = localClass.getMethod("onStop");
- stop.invoke(instance);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- @Override
- protected void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- try {
- Method des = localClass.getMethod("onDestroy");
- des.invoke(instance);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- public void LoadAPK(Bundle paramBundle, String dexpath, String dexoutputpath) {
- ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
- DexClassLoader localDexClassLoader = new DexClassLoader(dexpath, dexoutputpath, null, localClassLoader);
- try {
- PackageInfo plocalObject = getPackageManager().getPackageArchiveInfo(dexpath, 1);
- if ((plocalObject.activities != null) && (plocalObject.activities.length > 0)) {
- String activityname = plocalObject.activities[0].name;
- Log.d("sys", "activityname = " + activityname);
- localClass = localDexClassLoader.loadClass(activityname);//結果:"com.example.fragmentproject.FristActivity"
- mActivityClass = localClass;
- Constructor localConstructor = localClass.getConstructor(new Class[] {});
- instance = localConstructor.newInstance(new Object[] {});
- Log.d("sys", "instance = " + instance);
- mActivityInstance = instance;
- Method des = localClass.getMethod("test");
- des.invoke(instance);
- Method localMethodSetActivity = localClass.getDeclaredMethod("setActivity", new Class[] { Activity.class });
- localMethodSetActivity.setAccessible(true);
- localMethodSetActivity.invoke(instance, new Object[] { this });
- Method methodonCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
- methodonCreate.setAccessible(true);
- methodonCreate.invoke(instance, paramBundle);
- }
- return;
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
這是被調用的Activity:
- public class FristActivity extends Activity{
- private Button fragment;
- private Button listFragment;
- private Button controlFragment;
- private Button viewFlipper;
- private Button viewPager;
- private Activity otherActivity;
- public void test() {
- Log.i("sys", "測試方法執行了");
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // 測試DexClassLoader 動態加載未安裝Apk中的類
- TextView t = new TextView(otherActivity);
- t.setText("我是測試");
- otherActivity.setContentView(t);// R.layout.frist_activity_main
- Log.i("sys", "Fragment項目啓動了");
- }
- public void setActivity(Activity paramActivity) {
- Log.d("sys", "setActivity..." + paramActivity);
- this.otherActivity = paramActivity;
- }
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- Log.i("sys", "OnSaveInstance被調了");
- }
- @Override
- public void onStart() {
- Log.i("sys", "onStart被調了");
- // TODO Auto-generated method stub
- super.onStart();
- }
- @Override
- public void onResume() {
- Log.i("sys", "onResume被調了");
- // TODO Auto-generated method stub
- super.onResume();
- }
- @Override
- public void onPause() {
- Log.i("sys", "onPause被調了");
- // TODO Auto-generated method stub
- super.onPause();
- }
- @Override
- public void onStop() {
- Log.i("sys", "onStop被調了");
- // TODO Auto-generated method stub
- super.onStop();
- }
- @Override
- protected void onDestroy() {
- Log.i("sys", "onDestroy被調了");
- // TODO Auto-generated method stub
- super.onDestroy();
- }
- }
1.http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html
2.http://blog.csdn.net/mirkerson/article/details/8771723
3.http://blog.csdn.net/scliu0718/article/details/8438823
4.http://www.verydemo.com/demo_c131_i24569.html(Android 通過反射啓動未安裝的APK中的Activity的實例代碼)
5.http://www.myexception.cn/android/1217391.html(Android 通過反射啓動未安裝的APK中的Activity的實例圖形說明)