1. 程式人生 > >動態載入apk檔案並呼叫其程式碼

動態載入apk檔案並呼叫其程式碼

編寫一個apk,用於匯出方法,供其他apk呼叫

public class Dynamic implements IDynamic{
    private Activity mActivity;  

    public Dynamic() {
        // TODO Auto-generated constructor stub
    }
    @Override  
    public void init(Activity activity) {  
        mActivity = activity;  
        Log.i("TestDynamic"
, "In Dynamic::Init"); } @Override public void showBanner() { Toast.makeText(mActivity, "我是ShowBannber方法", 1500).show(); } @Override public void showDialog() { Toast.makeText(mActivity, "我是ShowDialog方法", 1500).show(); } @Override public
void showFullScreen() { Toast.makeText(mActivity, "我是ShowFullScreen方法", 1500).show(); } @Override public void showAppWall() { Toast.makeText(mActivity, "我是ShowAppWall方法", 1500).show(); } @Override public void destory() { } }

IDynamic是一個介面,用於外界可以使用介面進行呼叫

public interface IDynamic {
    /**初始化方法*/  
    public void init(Activity activity);  
    /**自定義方法*/  
    public void showBanner();  
    public void showDialog();  
    public void showFullScreen();  
    public void showAppWall();  
    /**銷燬方法*/  
    public void destory();
}

此外,在manifest.xml中,需要宣告一個Action,用於其他apk能通過Intent檢索到此apk

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="com.example.dynamiclib"/>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

,名字無所謂,只要其他apk通過Intent能檢索到此apk就可以

二、呼叫動態apk

根據目標apk包名,獲取其包路徑、catche路徑等資訊,建立DexClassLoader物件,並通過反射機制來呼叫動態apk中的功能函式程式碼

public class MainActivity extends Activity implements OnClickListener{

    private Button btn_show_banner;
    private Button btn_show_dialog;
    private Button btn_show_fullscreen;
    private Button btn_show_appwall;
    private final static String TAG = "TestDynamic";
    private IDynamic libInterface = null;
    private Method method_init = null;
    private Method method_showBanner = null;
    private Method method_showDialog = null;
    private Method method_showFullScreen = null;
    private Method method_showAppWall = null;
    private Object object = null;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_show_banner = (Button) findViewById(R.id.button1);
        btn_show_dialog = (Button) findViewById(R.id.button2);
        btn_show_fullscreen = (Button) findViewById(R.id.button3);
        btn_show_appwall = (Button) findViewById(R.id.button4);

        /**
         * 呼叫靜態apk時,需要有一個解壓路徑
         * **/
        File dexOutputDir = this.getDir("dex", 0);
        /**
         * 獲取apk存放的路徑,此apk未安裝,在磁碟上,也可能是網路臨時拉下來的
         * 這裡測試,放在SD卡根目錄
         * 主要是用於DexClassLoader的第一個引數使用
         * **/
        String dexPath = Environment.getExternalStorageDirectory().toString() 
                + File.separator 
                + "DynamicLib.apk";
        Log.i(TAG, "ApkPath = "+dexPath);
        Log.i(TAG, "dexOutPath = "+dexOutputDir.getAbsolutePath());


        /**
         * 與被呼叫的apk里名稱約定要一致
         * 用來找到指定apk,系統中多個時,下面get要注意取哪一個
         * **/
        Intent intent = new Intent("com.example.dynamiclib");
        /**
         * 獲取包管理器
         * **/
        PackageManager pm = getPackageManager();
        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
        /**
         * 獲得指定Activity資訊
         * 如果有兩個名字都是“com.example.dynamiclib”的Intent,
         * 則可能需要get(1),因為它會返回兩個activity
         * **/
        ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
        /**
         * 獲得apk的目錄或者jar的目錄    
         * **/
        String apkPath = activityInfo.applicationInfo.sourceDir;    
        /**
         * native程式碼的目錄    
         * **/
        String libPath = activityInfo.applicationInfo.nativeLibraryDir;  
        /**
         * 第一種方法
         * 建立DexClassLoader
         * 把apk載入到虛擬機器中
         * 注意:這裡的路徑可以使用兩種
         *      即可以是dexPath(磁碟上的靜態檔案)
         *      也可以是通過pm獲取的已經安裝的apk的路徑
         * **/

        DexClassLoader dcl = new DexClassLoader(dexPath, 
                dexOutputDir.getAbsolutePath(), 
                null, 
                this.getClass().getClassLoader());

        /**
         * 第二種方法
         * 使用PathClassLoader
         * 這裡只能找到已經安裝的apk
         * 並且路徑,必須通過Intent、PM來聯合獲取,否則不能成功
         * 此外,Intent裡的名字,如果系統裡存在兩個名字相同的apk,name可能還需要試試第二個
         * 因為不一定哪個在前,哪個在後
         * **/
        //PathClassLoader dcl = new PathClassLoader(apkPath, libPath, this.getClass().getClassLoader());

        Log.i(TAG, "DexClassLoader ="+dcl);
        try {
            Log.i(TAG, "before dcl.loadClass");
            /**
             * 載入目標class,名稱為目標類名
             * **/
            Class<?> orgClass = dcl.loadClass("com.example.dynamiclib.Dynamic");
            Log.i(TAG, "orgClass load is " + orgClass.getClassLoader().toString());
            object = orgClass.newInstance();
            Class[] param = new Class[1];
            param[0] = Activity.class;
            method_init = orgClass.getMethod("init", param);
            method_showBanner = orgClass.getMethod("showBanner");
            method_showDialog = orgClass.getMethod("showDialog");
            method_showFullScreen = orgClass.getMethod("showFullScreen");
            method_showAppWall = orgClass.getMethod("showAppWall");
            if (method_init != null) {
                method_init.invoke(object, this);
            }
            Log.i(TAG, "libInterFace is "+libInterface);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            Log.i(TAG, "Not Found Class");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        btn_show_banner.setOnClickListener(this);
        btn_show_dialog.setOnClickListener(this);
        btn_show_fullscreen.setOnClickListener(this);
        btn_show_appwall.setOnClickListener(this);

    }
    void ShowTips(String tips)
    {
        Toast.makeText(this, tips, Toast.LENGTH_LONG).show();
    }
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.button1:
            if (method_showBanner != null) {
                try {
                    method_showBanner.invoke(object);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            else {
                ShowTips("類載入失敗");
            }
            break;
        case R.id.button2:
            if (method_showDialog != null) {
                try {
                    method_showDialog.invoke(object);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            else {
                ShowTips("類載入失敗");
            }
            break;
        case R.id.button3:
            if (method_showFullScreen != null) {
                try {
                    method_showFullScreen.invoke(object);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            else {
                ShowTips("類載入失敗");
            }
            break;
        case R.id.button4:
            if (method_showAppWall != null) {
                try {
                    method_showAppWall.invoke(object);
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            else {
                ShowTips("類載入失敗");
            }
            break;
        default:
            break;
        }
    }
}
  1. 這裡是此處通過反射機制來呼叫程式碼,還沒有完成通過介面來呼叫的功能,待繼續除錯
  2. 這裡是從已經安裝的apk獲取路徑等資訊,如果是目錄下靜態的apk(並沒有安裝到手機上),直接把構建DexClassLoader的第一個引數改為靜態apk的絕對路徑,其他不需要變化,即可正常執行;
  3. 不過如果使用PathClassLoader,來載入,必須通過intent查詢目標apk,同時目標apk必須已經安裝,因為它只能載入固定目錄下的apk,不能隨意路徑的apk
  4. 使用了IDynamic後,需要把對應的jar包引入到工程裡的lib中,通過“Build Path”->”Configure Build Path”裡進行新增;只把jar拷貝到這個目錄是沒有用的