美文网首页
插件化 -- Dynamic-load-apk

插件化 -- Dynamic-load-apk

作者: TomyZhang | 来源:发表于2019-10-24 12:19 被阅读0次

一、基础

1.资源管理

获取apk资源原理

Android工程在打包apk时会将资源名与资源id在R.java中一一映射起来:

public final class R {
    ...
    public static final class string { //字符串资源
        public static final int app_name=0x7f070000; //资源名:app_name,资源id:0x7f070000
    }
    ...
}

获取资源方法:

Resources resources = getResources(); //获取Resources对象 
int resourceId = R.string.app_name; //通过资源名获取资源id 
String str = resources.getString(resourceId); //通过Resources对象以及资源id获取资源

源码分析:

//Resources.java
/**
 * Create a new Resources object on top of an existing set of assets in an
 * AssetManager.
 *
 * @deprecated Resources should not be constructed by apps.
 * See {@link android.content.Context#createConfigurationContext(Configuration)}.
 *
 * @param assets Previously created AssetManager.
 * @param metrics Current display metrics to consider when
 *                selecting/computing resource values.
 * @param config Desired device configuration to consider when
 *               selecting/computing resource values (optional).
 */
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
    this(null);
    mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}


//AssetManager.java
/**
 * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
 * @hide
 */
@Deprecated
public int addAssetPath(String path) {
    return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}

将apk的path传入,包装一个AssetManager,然后用AssetManager生成Resources,通过Resources可以获取apk的资源。

获取插件apk资源

例子:(三星G9500,P OS)

//步骤1.创建一个插件apk工程module:plugin,其中res/values/strings.xml内容如下:
<resources>
    <string name="app_name">plugin</string>
</resources>

//步骤2.生成插件apk:plugin-debug.apk,并放到手机根目录

//步骤3.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
    private static final String TAG = "HostClass";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
        Log.d(TAG, "zwm, apkPath: " + apkPath);
        File file = new File(apkPath);
        if(!file.exists()) {
            Log.d(TAG, "zwm, file not exist");
            return;
        }
        Resources pluginResources = getPluginResources(apkPath);
        Log.d(TAG, "zwm, pluginResources: " + pluginResources);
        int pluginResId = getPluginResId(apkPath, "com.tomorrow.plugin", "app_name");
        Log.d(TAG, "zwm, pluginResId: " + pluginResId);
        String pluginStr = pluginResources.getString(pluginResId);
        Log.d(TAG, "zwm, pluginStr: " + pluginStr);
    }

    public Resources getPluginResources(String pluginPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            //通过反射调用方法addAssetPath(String path)
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            //将插件apk文件路径添加进AssetManager
            addAssetPath.invoke(assetManager, pluginPath);
            //获取宿主apk的Resources对象
            Resources superRes = getResources();
            //创建插件apk的Resources对象
            Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
            return mResources;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public int getPluginResId(String pluginPath, String apkPackageName, String resName) {
        try {
            //创建类加载器对象,加载指定路径apk文件
            PathClassLoader classLoader = new PathClassLoader(pluginPath, null, getClassLoader());
            //通过反射获取资源id
            Class<?> clazz = classLoader.loadClass(apkPackageName + ".R$string");
            Field field = clazz.getField(resName);
            return (int)field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}

//步骤4.运行宿主apk,输出log如下:
2019-09-17 17:10:40.051 HostClass: zwm, apkPath: /storage/emulated/0/plugin-debug.apk
2019-09-17 17:10:40.078 HostClass: zwm, pluginResources: android.content.res.Resources@1ea7b93
2019-09-17 17:10:40.092 HostClass: zwm, pluginResId: 2131165184
2019-09-17 17:10:40.093 HostClass: zwm, pluginStr: plugin

2.Activity生命周期管理

Activity组件是需要在manifest中注册后才能以标准Intent的方式启动,通过ClassLoader加载并实例化的Activity实例只是一个普通的Java对象,能调用对象的方法,但是它没有生命周期。

代理Activity模式

主项目APK注册一个代理Activity(命名为ProxyActivity),ProxyActivity是一个普通的Activity,但只是一个空壳,自身并没有什么业务逻辑。每次打开插件APK里的某一个Activity的时候,都是在主项目里使用标准的方式启动ProxyActivity,再在ProxyActivity的生命周期里同步调用插件中的Activity实例的生命周期方法,从而执行插件APK的业务逻辑。

用反射调用插件Activity相应生命周期:

//步骤1.创建一个插件apk工程module:plugin,并创建一个插件类PluginActivity,代码如下:
public class PluginActivity {
    private static final String TAG = "PluginActivity";

    protected void onResume() {
        Log.d(TAG, "zwm, Plugin Activity onResume");
    }

    protected void onPause() {
        Log.d(TAG, "zwm, Plugin Activity onPause");
    }

    protected void onDestroy() {
        Log.d(TAG, "zwm, Plugin Activity onDestroy");
    }
}

//步骤2.生成插件apk:plugin-debug.apk,并放到手机根目录

//步骤3.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
    private static final String TAG = "HostActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, Host Activity onCreate");
        setContentView(R.layout.activity_main);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startPluginActivity();
            }
        }, 2000);
    }

    private void startPluginActivity() {
        Log.d(TAG, "zwm, Host Activity startPluginActivity");
        Intent intent = new Intent(this, ProxyActivity.class);
        startActivity(intent);
    }
}

//步骤4.创建一个代理类ProxyActivity,代码如下:
(需要在manifest中注册activity:<activity android:name=".ProxyActivity"/>)
public class ProxyActivity extends Activity {
    private static final String TAG = "ProxyActivity";
    private Class<?> pluginActivity;
    private Object pluginInstance;
    private Method onResume;
    private Method onPause;
    private Method onDestroy;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, Proxy Activity onCreate");
        init();
    }

    private void init() {
        String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
        Log.d(TAG, "zwm, Proxy Activity init, apkPath: " + apkPath);
        DexClassLoader classLoader = new DexClassLoader(apkPath, null, null, getClassLoader());
        try {
            pluginActivity = classLoader.loadClass("com.tomorrow.plugin.PluginActivity");
            Log.d(TAG, "zwm, pluginActivity: " + pluginActivity);
            pluginInstance = pluginActivity.newInstance();
            Log.d(TAG, "zwm, pluginInstance: " + pluginInstance);
            onResume = pluginActivity.getDeclaredMethod("onResume", null);
            onResume.setAccessible(true);
            Log.d(TAG, "zwm, onResume: " + onResume);
            onPause = pluginActivity.getDeclaredMethod("onPause", null);
            onPause.setAccessible(true);
            Log.d(TAG, "zwm, onPause: " + onPause);
            onDestroy = pluginActivity.getDeclaredMethod("onDestroy", null);
            onDestroy.setAccessible(true);
            Log.d(TAG, "zwm, onDestroy: " + onDestroy);
        } catch (ClassNotFoundException e) {
                e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "zwm, Proxy Activity onResume");
        if(onResume != null) {
            try {
                onResume.invoke(pluginInstance, null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "zwm, Proxy Activity onPause");
        if(onPause != null) {
            try {
                onPause.invoke(pluginInstance, null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "zwm, Proxy Activity onDestroy");
        if(onDestroy != null) {
            try {
                onDestroy.invoke(pluginInstance, null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

//步骤5.运行宿主apk,输出log如下:
2019-10-22 17:05:56.714 D/HostActivity: zwm, Host Activity onCreate
2019-10-22 17:05:58.806 D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-22 17:05:58.947 D/ProxyActivity: zwm, Proxy Activity onCreate
2019-10-22 17:05:58.948 D/ProxyActivity: zwm, Proxy Activity init, apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-22 17:05:58.961 D/ProxyActivity: zwm, pluginActivity: class com.tomorrow.plugin.PluginActivity
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, pluginInstance: com.tomorrow.plugin.PluginActivity@ae56d61
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onResume: protected void com.tomorrow.plugin.PluginActivity.onResume()
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onPause: protected void com.tomorrow.plugin.PluginActivity.onPause()
2019-10-22 17:05:58.962 D/ProxyActivity: zwm, onDestroy: protected void com.tomorrow.plugin.PluginActivity.onDestroy()
2019-10-22 17:05:58.993 D/ProxyActivity: zwm, Proxy Activity onResume
2019-10-22 17:05:58.994 D/PluginActivity: zwm, Plugin Activity onResume
2019-10-22 17:06:04.908 D/ProxyActivity: zwm, Proxy Activity onPause
2019-10-22 17:06:04.908 D/PluginActivity: zwm, Plugin Activity onPause
2019-10-22 17:06:05.498 D/ProxyActivity: zwm, Proxy Activity onDestroy
2019-10-22 17:06:05.498 D/PluginActivity: zwm, Plugin Activity onDestroy

用接口方式调用插件Activity相应生命周期:

//步骤1.创建一个插件apk工程module:plugin,并创建一个插件类PluginActivity,代码如下:
public class PluginActivity implements DLPlugin {
    private static final String TAG = "PluginActivity";

    @Override
    public void onResume() {
        Log.d(TAG, "zwm, Plugin Activity onResume");
    }

    @Override
    public void onPause() {
        Log.d(TAG, "zwm, Plugin Activity onPause");
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "zwm, Plugin Activity onDestroy");
    }
}

//步骤2.在插件com.tomorrow.interfaces包中创建接口DLPlugin,代码如下:
package com.tomorrow.interfaces;
public interface DLPlugin {
    public void onResume();
    public void onPause();
    public void onDestroy();
}

//步骤3.生成插件apk:plugin-debug.apk,并放到手机根目录

//步骤4.创建一个宿主apk工程module:app,其中MainActivity代码如下:
(需要在manifest中添加权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>)
public class MainActivity extends Activity {
    private static final String TAG = "HostActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, Host Activity onCreate");
        setContentView(R.layout.activity_main);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startPluginActivity();
            }
        }, 2000);
    }

    private void startPluginActivity() {
        Log.d(TAG, "zwm, Host Activity startPluginActivity");
        Intent intent = new Intent(this, ProxyActivity.class);
        startActivity(intent);
    }
}

//步骤5.创建一个代理类ProxyActivity,代码如下:
(需要在manifest中注册activity:<activity android:name=".ProxyActivity"/>)
public class ProxyActivity extends Activity {
    private static final String TAG = "ProxyActivity";
    private Class<?> pluginActivity;
    private DLPlugin pluginInstance;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, Proxy Activity onCreate");
        init();
    }

    private void init() {
        String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
        Log.d(TAG, "zwm, Proxy Activity init, apkPath: " + apkPath);
        DexClassLoader classLoader = new DexClassLoader(apkPath, null, null, getClassLoader());
        try {
            pluginActivity = classLoader.loadClass("com.tomorrow.plugin.PluginActivity");
            Log.d(TAG, "zwm, pluginActivity: " + pluginActivity);
            pluginInstance = (DLPlugin) pluginActivity.newInstance();
            Log.d(TAG, "zwm, pluginInstance: " + pluginInstance);
        } catch (ClassNotFoundException e) {
                e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "zwm, Proxy Activity onResume");
        pluginInstance.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "zwm, Proxy Activity onPause");
        pluginInstance.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "zwm, Proxy Activity onDestroy");
        pluginInstance.onDestroy();
    }
}

//步骤6.在宿主com.tomorrow.interfaces包中创建接口DLPlugin,代码如下:
package com.tomorrow.interfaces;
public interface DLPlugin {
    public void onResume();
    public void onPause();
    public void onDestroy();
}

//步骤7.运行宿主apk,输出log如下:
2019-10-22 18:34:06.470 D/HostActivity: zwm, Host Activity onCreate
2019-10-22 18:34:09.979 D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-22 18:34:10.212 D/ProxyActivity: zwm, Proxy Activity onCreate
2019-10-22 18:34:10.215 D/ProxyActivity: zwm, Proxy Activity init, apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-22 18:34:10.238 D/ProxyActivity: zwm, pluginActivity: class com.tomorrow.plugin.PluginActivity
2019-10-22 18:34:10.239 D/ProxyActivity: zwm, pluginInstance: com.tomorrow.plugin.PluginActivity@4b81192
2019-10-22 18:34:10.387 D/ProxyActivity: zwm, Proxy Activity onResume
2019-10-22 18:34:10.387 D/PluginActivity: zwm, Plugin Activity onResume
2019-10-22 18:34:10.438 D/ProxyActivity: zwm, Proxy Activity onPause
2019-10-22 18:34:10.438 D/PluginActivity: zwm, Plugin Activity onPause

二、使用(三星G9500,P OS)

//步骤1.从GitHub下载Dynamic-load-apk源码:https://github.com/singwhatiwanna/dynamic-load-apk

//步骤2.编译lib模块生成lib-debug.aar

//步骤3.将lib-debug.aar放入主项目模块及插件模块libs目录下

//步骤4.修改主项目模块及插件模块build.gradle,代码如下:
repositories {
    flatDir{
        dirs 'libs'
    }
}

dependencies {
    implementation(name: 'lib-debug', ext: 'aar')
}

//步骤5.插件模块两个Activity及对应的布局文件内容如下:
//MainActivity
public class MainActivity extends DLBasePluginActivity {
    private static final String TAG = "PluginActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "zwm, Plugin Activity onCreate");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "zwm, Plugin Activity onResume");
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "zwm, Plugin Activity startPluginActivity");
                DLIntent intent = new DLIntent(getPackageName(), PluginActivity2.class);
                startPluginActivity(intent);
            }
        }, 2000);
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "zwm, Plugin Activity onPause");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "zwm, Plugin Activity onDestroy");
    }
}

//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is Plugin Activity"
        android:textSize="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

//PluginActivity2
public class PluginActivity2 extends DLBasePluginActivity {
    private static final String TAG = "PluginActivity2";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);
        Log.d(TAG, "zwm, Plugin Activity2 onCreate");
        Log.d(TAG, "zwm, Plugin Activity2 this: " + this);
        Log.d(TAG, "zwm, Plugin Activity2 that: " + that);

        TextView textView = that.findViewById(R.id.textview);
        String text = textView.getText().toString();
        Toast.makeText(that.getApplicationContext(), text, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "zwm, Plugin Activity2 onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "zwm, Plugin Activity2 onPause");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "zwm, Plugin Activity2 onDestroy");
    }
}

//activity_2.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is Plugin Activity2"
        android:textSize="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

//步骤6.插件模块manifest文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.plugin">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".PluginActivity2"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

//步骤7.生成插件apk:plugin-debug.apk,并放到手机根目录

//步骤8.主项目模块Activity及对应的布局文件内容如下:
//MainActivity
public class MainActivity extends Activity {
    private static final String TAG = "HostActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, Host Activity onCreate");
        setContentView(R.layout.activity_main);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startPluginActivity();
            }
        }, 2000);
    }

    private void startPluginActivity() {
        Log.d(TAG, "zwm, Host Activity startPluginActivity");
        String apkPath = Environment.getExternalStorageDirectory() + "/plugin-debug.apk";
        Log.d(TAG, "zwm, Host Activity apkPath: " + apkPath);
        File file = new File(apkPath);
        if(!file.exists()) {
            Log.d(TAG, "zwm, Host Activity file not exist");
            return;
        }
        PluginItem item = new PluginItem();
        item.pluginPath = apkPath;
        Log.d(TAG, "zwm, Host Activity pluginPath: " + item.pluginPath);
        item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
        Log.d(TAG, "zwm, Host Activity packageInfo: " + item.packageInfo);
        if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
            item.launcherActivityName = item.packageInfo.activities[0].name;
            Log.d(TAG, "zwm, Host Activity launcherActivityName: " + item.launcherActivityName);
        }
        if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
            item.launcherServiceName = item.packageInfo.services[0].name;
            Log.d(TAG, "zwm, Host Activity launcherServiceName: " + item.launcherServiceName);
        }

        DLPluginManager pluginManager = DLPluginManager.getInstance(this);
        DLPluginPackage dlPluginPackage = pluginManager.loadApk(item.pluginPath);
        Log.d(TAG, "zwm, Host Activity loadApk, dlPluginPackage: " + dlPluginPackage);
        int result = pluginManager.startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName));
        Log.d(TAG, "zwm, Host Activity startPluginActivity, result: " + result);
    }

    public static class PluginItem {
        public PackageInfo packageInfo;
        public String pluginPath;
        public String launcherActivityName;
        public String launcherServiceName;

        public PluginItem() {
        }
    }
}

//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is Host Activity"
        android:textSize="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

//步骤9.主项目模块manifest文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.testnetworkcache">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.ryg.dynamicload.DLProxyActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="com.ryg.dynamicload.proxy.activity.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

//步骤10.运行宿主apk,输出log如下:
2019-10-23 13:21:45.129 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity onCreate
2019-10-23 13:21:47.278 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity startPluginActivity
2019-10-23 13:21:47.291 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity apkPath: /storage/emulated/0/plugin-debug.apk
2019-10-23 13:21:47.293 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity pluginPath: /storage/emulated/0/plugin-debug.apk
2019-10-23 13:21:47.347 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity packageInfo: PackageInfo{8d48aea com.tomorrow.plugin}
2019-10-23 13:21:47.347 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity launcherActivityName: com.tomorrow.plugin.MainActivity
2019-10-23 13:21:47.442 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity dlPluginPackage: com.ryg.dynamicload.internal.DLPluginPackage@ae86bb6
2019-10-23 13:21:47.484 12010-12010/com.tomorrow.testnetworkcache D/HostActivity: zwm, Host Activity result: 0
2019-10-23 13:21:47.615 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onCreate
2019-10-23 13:21:47.619 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onResume
2019-10-23 13:21:49.624 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity startPluginActivity
2019-10-23 13:21:49.704 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity: zwm, Plugin Activity onPause
2019-10-23 13:21:49.805 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onCreate
2019-10-23 13:21:49.806 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 this: com.tomorrow.plugin.PluginActivity2@a89aa52
2019-10-23 13:21:49.806 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 that: com.ryg.dynamicload.DLProxyActivity@beeb123
2019-10-23 13:21:49.933 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onResume
2019-10-23 13:22:01.939 12010-12010/com.tomorrow.testnetworkcache D/PluginActivity2: zwm, Plugin Activity2 onPause

三、原理

Dynamic-load-apk原理的核心思想可以总结为两个字:代理。通过在manifest中注册代理组件,当启动插件组件时首先启动一个代理组件,然后通过这个代理组件来构建、启动插件组件。

DLPluginManager.getInstance(this).loadApk(item.pluginPath)

//加载插件APK
public DLPluginPackage loadApk(String dexPath) {
    // when loadApk is called by host apk, we assume that plugin is invoked
    // by host.
    return loadApk(dexPath, true); //调用重载方法加载插件APK
}

public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
            PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); //获取插件APK的PackageInfo对象
    if (packageInfo == null) {
        return null;
    }

    DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath); //准备好插件使用环境
    if (hasSoLib) {
        copySoLib(dexPath); //拷贝插件的so库到指定目录
    }

    return pluginPackage;
}

private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {

    DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName); //获取缓存
    if (pluginPackage != null) {
        return pluginPackage;
    }
    DexClassLoader dexClassLoader = createDexClassLoader(dexPath); //创建DexClassLoader
    AssetManager assetManager = createAssetManager(dexPath); //创建AssetManager
    Resources resources = createResources(assetManager); //创建Resources
    // create pluginPackage
    pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo); //封装到DLPluginPackage
    mPackagesHolder.put(packageInfo.packageName, pluginPackage); //存入缓存
    return pluginPackage; //返回DLPluginPackage
}

//注:@param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
private DexClassLoader createDexClassLoader(String dexPath) {
    File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
    dexOutputPath = dexOutputDir.getAbsolutePath();
    DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader()); //创建DexClassLoader
    return loader;
}

private AssetManager createAssetManager(String dexPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, dexPath); //反射调用AssetManager#addAssetPath方法,加载插件APK资源
        return assetManager;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

private Resources createResources(AssetManager assetManager) {
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); //创建Resources
    return resources;
}

DLPluginManager.getInstance(this).startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName))

//启动插件Activity
public int startPluginActivity(Context context, DLIntent dlIntent) {
    return startPluginActivityForResult(context, dlIntent, -1);
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    if (mFrom == DLConstants.FROM_INTERNAL) { //启动主项目模块内部的Activity
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        performStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    String packageName = dlIntent.getPluginPackage(); //获取插件APK包名
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }

    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); //获取插件APK对应的DLPluginPackage缓存
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }

    final String className = getPluginActivityFullPath(dlIntent, pluginPackage); //获取要启动的插件Activity类的全限定名
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); //加载要启动的插件Activity类
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }

    // get the proxy activity class, the proxy activity will launch the
    // plugin activity.
    Class<? extends Activity> activityClass = getProxyActivityClass(clazz); //根据要启动的插件Activity类获取对应的代理Activity类
    if (activityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }

    // put extra data
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    dlIntent.setClass(mContext, activityClass);
    performStartActivityForResult(context, dlIntent, requestCode); //启动代理Activity
    return START_RESULT_SUCCESS;
}

private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
    Class<?> clazz = null;
    try {
        clazz = Class.forName(className, true, classLoader); //使用指定的类加载器加载指定的类
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    return clazz;
}

private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
    Class<? extends Activity> activityClass = null;
    if (DLBasePluginActivity.class.isAssignableFrom(clazz)) { //如果clazz是DLBasePluginActivity的子类或者子接口
        activityClass = DLProxyActivity.class; //返回代理Activity:DLProxyActivity
    } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) { //如果clazz是DLBasePluginFragmentActivity的子类或者子接口
        activityClass = DLProxyFragmentActivity.class; //返回代理Activity:DLProxyFragmentActivity
    }

    return activityClass;
}

public class DLProxyActivity extends Activity implements DLAttachable

//代理Activity
public class DLProxyActivity extends Activity implements DLAttachable {

    protected DLPlugin mRemoteActivity; //插件Activity
    private DLProxyImpl impl = new DLProxyImpl(this); //代理逻辑类

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        impl.onCreate(getIntent()); //代理逻辑初始化
    }

    @Override
    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
        mRemoteActivity = remoteActivity; //插件Activity赋值
    }

    @Override
    public AssetManager getAssets() { //获取AssetManager
        return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
    }

    @Override
    public Resources getResources() { //获取Resources
        return impl.getResources() == null ? super.getResources() : impl.getResources();
    }

    @Override
    public Theme getTheme() { //获取Theme
        return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
    }

    @Override
    public ClassLoader getClassLoader() { //获取ClassLoader
        return impl.getClassLoader();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        mRemoteActivity.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onStart() {
        mRemoteActivity.onStart();
        super.onStart();
    }

    @Override
    protected void onRestart() {
        mRemoteActivity.onRestart();
        super.onRestart();
    }

    @Override
    protected void onResume() {
        mRemoteActivity.onResume();
        super.onResume();
    }

    @Override
    protected void onPause() {
        mRemoteActivity.onPause();
        super.onPause();
    }

    @Override
    protected void onStop() {
        mRemoteActivity.onStop();
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        mRemoteActivity.onDestroy();
        super.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        mRemoteActivity.onSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        mRemoteActivity.onRestoreInstanceState(savedInstanceState);
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        mRemoteActivity.onNewIntent(intent);
        super.onNewIntent(intent);
    }

    @Override
    public void onBackPressed() {
        mRemoteActivity.onBackPressed();
        super.onBackPressed();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        return mRemoteActivity.onTouchEvent(event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        super.onKeyUp(keyCode, event);
        return mRemoteActivity.onKeyUp(keyCode, event);
    }

    @Override
    public void onWindowAttributesChanged(LayoutParams params) {
        mRemoteActivity.onWindowAttributesChanged(params);
        super.onWindowAttributesChanged(params);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        mRemoteActivity.onWindowFocusChanged(hasFocus);
        super.onWindowFocusChanged(hasFocus);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        mRemoteActivity.onCreateOptionsMenu(menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        mRemoteActivity.onOptionsItemSelected(item);
        return super.onOptionsItemSelected(item);
    }
    
    @Override
    public ComponentName startService(Intent service) {
        return super.startService(service);
    }
}

//代理逻辑类
public class DLProxyImpl {

    private static final String TAG = "DLProxyImpl";

    private String mClass;
    private String mPackageName;

    private DLPluginPackage mPluginPackage;
    private DLPluginManager mPluginManager;

    private AssetManager mAssetManager;
    private Resources mResources;
    private Theme mTheme;

    private ActivityInfo mActivityInfo;
    private Activity mProxyActivity; //代理Activity
    protected DLPlugin mPluginActivity; //插件Activity
    public ClassLoader mPluginClassLoader;

    public DLProxyImpl(Activity activity) {
        mProxyActivity = activity;
    }

    private void initializeActivityInfo() {
        PackageInfo packageInfo = mPluginPackage.packageInfo;
        if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {
            if (mClass == null) {
                mClass = packageInfo.activities[0].name;
            }

            //Finals 修复主题BUG
            int defaultTheme = packageInfo.applicationInfo.theme;
            for (ActivityInfo a : packageInfo.activities) {
                if (a.name.equals(mClass)) {
                    mActivityInfo = a;
                    // Finals ADD 修复主题没有配置的时候插件异常
                    if (mActivityInfo.theme == 0) {
                        if (defaultTheme != 0) {
                            mActivityInfo.theme = defaultTheme;
                        } else {
                            if (Build.VERSION.SDK_INT >= 14) {
                                mActivityInfo.theme = android.R.style.Theme_DeviceDefault;
                            } else {
                                mActivityInfo.theme = android.R.style.Theme;
                            }
                        }
                    }
                }
            }

        }
    }

    private void handleActivityInfo() {
        Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme);
        if (mActivityInfo.theme > 0) {
            mProxyActivity.setTheme(mActivityInfo.theme);
        }
        Theme superTheme = mProxyActivity.getTheme();
        mTheme = mResources.newTheme();
        mTheme.setTo(superTheme);
        // Finals适配三星以及部分加载XML出现异常BUG
        try {
            mTheme.applyStyle(mActivityInfo.theme, true);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // TODO: handle mActivityInfo.launchMode here in the future.
    }

    public void onCreate(Intent intent) {
        Log.d(TAG, "zwm, onCreate");
        // set the extra's class loader
        intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);

        mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE); //要启动的插件APK包名
        mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS); //要启动的插件Activity类名
        Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);

        mPluginManager = DLPluginManager.getInstance(mProxyActivity);
        mPluginPackage = mPluginManager.getPackage(mPackageName); //获取插件APK对应的DLPluginPackage缓存
        mAssetManager = mPluginPackage.assetManager; //获取插件APK对应的AssetManager
        mResources = mPluginPackage.resources; //获取插件APK对应的Resources

        initializeActivityInfo(); //获取插件Activity主题
        handleActivityInfo(); //给代理Activity设置主题
        launchTargetActivity(); //启动插件Activity
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    protected void launchTargetActivity() {
        try {
            Class<?> localClass = getClassLoader().loadClass(mClass); //加载插件Activity类
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {}); //创建插件Activity对象
            mPluginActivity = (DLPlugin) instance; //使用接口引用
            ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager); //将插件Activity对象传递给代理Activity对象
            Log.d(TAG, "instance = " + instance);
            // attach the proxy activity and plugin package to the mPluginActivity
            mPluginActivity.attach(mProxyActivity, mPluginPackage); //将代理Activity对象传递给插件Activity对象

            Bundle bundle = new Bundle();
            bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
            mPluginActivity.onCreate(bundle); //调用插件Activity的onCreate方法
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ClassLoader getClassLoader() {
        return mPluginPackage.classLoader;
    }

    public AssetManager getAssets() {
        return mAssetManager;
    }

    public Resources getResources() {
        return mResources;
    }

    public Theme getTheme() {
        return mTheme;
    }

    public DLPlugin getRemoteActivity() {
        return mPluginActivity;
    }
}

插件内部资源加载

//public class PluginActivity extends DLBasePluginActivity
String name = getResources().getString(R.string.app_name); //通过DLBasePluginActivity获取Resources

//public class DLBasePluginActivity extends Activity implements DLPlugin
public Resources getResources() { //启动插件Activity时mFrom值为1
    return this.mFrom == 0 ? super.getResources() : this.mProxyActivity.getResources(); //通过DLProxyActivity获取Resources
}

//public class DLProxyActivity extends Activity implements DLAttachable
@Override
public Resources getResources() {
    return impl.getResources() == null ? super.getResources() : impl.getResources(); //通过DLProxyImpl获取Resources
}

//public class DLProxyImpl
public Resources getResources() {
    return mResources; //插件APK对应的Resources
}

//public class DLProxyImpl
public void onCreate(Intent intent) {
    ...
    mPluginManager = DLPluginManager.getInstance(mProxyActivity);
    mPluginPackage = mPluginManager.getPackage(mPackageName); //获取插件APK对应的DLPluginPackage缓存
    mAssetManager = mPluginPackage.assetManager; //获取插件APK对应的AssetManager
    mResources = mPluginPackage.resources; //获取插件APK对应的Resources
    ...
} 

相关链接

Android插件化入门指南

相关文章

网友评论

      本文标题:插件化 -- Dynamic-load-apk

      本文链接:https://www.haomeiwen.com/subject/bkvrvctx.html