使用ClassLoader加载插件类

初始化DexClassLoader

这里假设插件类在SDCard上,我们可以使用DexClassLoader加载这个类,使用方法也很简单。

// Dex解压目录
mDexOutPath = getDir("dex", MODE_PRIVATE);
// 这个依赖的lib路径,可以传null
mLibPath = getDir("pluginlib", MODE_PRIVATE);
// 实际的Apk文件,这里我们随便模拟一个
// 但是这个Apk需要真实存在
mDexPath = SDCARD.getAbsolutePath() + "/plugin_test/plugina-debug.apk";
// 创建DexClassLoader
mMyClassLoader = new DexClassLoader(mDexPath
        , mDexOutPath.getAbsolutePath(), mLibPath.getAbsolutePath(), getClassLoader());

使用反射调用加载类的生命周期

由于我们通过反射创建的这个类实例,所以它没有向AMS注册,就没有相关的生命周期回调。这里我们通过代理的方式解决这个问题,即启动一个宿主Activity,他的生命周期是受系统管理的,然后让这个Activity代理插件Activity的声明周期即可。

// 插件Activity基类提供一个代理Activity的设置方法。
/**
 * Created by yangtianrui on 2018/3/4.
 * 与宿主Activity交互
 */

@SuppressLint("Registered")
public class BaseProxyActivity extends Activity {


    protected Activity mDelegate;


    public void setProxy(Activity delegate) {
        mDelegate = delegate;
    }


    @Override
    public void setContentView(View view) {
        if (mDelegate == null) {
            super.setContentView(view);
        } else {
            mDelegate.setContentView(view);
        }
    }
}
// 插件A的Activity代码
public class PluginMainActivity extends BaseProxyActivity {

    private static final String TAG = "pluginA";

    // onCreate()之类的生命周期,由宿主负责回调
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (mDelegate == null) {
            super.onCreate(savedInstanceState);
        }

        Log.d(TAG, "PluginA Activity onCreate: ");
        //setContentView(createContentView());

        if (mDelegate != null) {
            // 如果不重写
            mDelegate.setContentView(R.layout.plugin_a_activity);
        } else {
            setContentView(R.layout.plugin_a_activity);
        }
    }

    private View createContentView() {
        final Context context = mDelegate != null ? mDelegate : this;
        final TextView tv = new TextView(context);
        tv.setText("PluginA Activity");
        tv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT
                , ViewGroup.LayoutParams.WRAP_CONTENT));
        tv.setGravity(Gravity.CENTER);
        return tv;
    }
}

宿主中加载插件Activity的代码

try {
            //1.先加载到类
            // 使用ClassLoader#loadClass时,需要完成的报名和类名。
            // com.example.plugina.PluginMainActivity
            final Class clazz = mMyClassLoader.loadClass(packageInfo.activities[0].name);
            Log.d(TAG, "onCreate: get class " + clazz.toString());
            // 2. 获取构造器对象
            Log.d(TAG, "onCreate: constructors: " + Arrays.toString(clazz.getConstructors()));
            Constructor<?> constructor = clazz.getConstructor();
            if (constructor == null) {
                return;
            }

            // 3. 初始化对象
            final Object remote = constructor.newInstance();
            // 4. 回调onCreate()
            // fixme getDeclaredMethod和getMethod区别
            final Method onCreate = clazz.getDeclaredMethod("onCreate", Bundle.class);
            onCreate.setAccessible(true);
            final Method setProxy = clazz.getMethod("setProxy", Activity.class);
            setProxy.setAccessible(true);

            setProxy.invoke(remote, this);
            onCreate.invoke(remote, savedInstanceState);

        } catch (Exception e) {
            e.printStackTrace();
        }

这个补充一个小知识点, getMethod()和getDecaledMethod()的区别,getMethod()的作用是获取当前类和父类的所有public方法,但是没有办法获取所有的非public方法。getDecaledMethod()可以获取当前类的所有方法,但是没有办法获取父类的任何方法,注意别弄混了。

资源管理

资源管理实际上是通过ContextImpl执行的,只要我们重写这个getResource(),和getAsset()资源的问题就解决了。

/** Return an AssetManager instance for your application's package. */
    public abstract AssetManager getAssets();

    /** Return a Resources instance for your application's package. */
    public abstract Resources getResources();

宿主中的资源加载

// 宿主Activity的onCreate中调用
 private void loadResource() {
        try {
            AssetManager asset = AssetManager.class.newInstance();
            Method addAsset = AssetManager.class.getMethod("addAssetPath", String.class);
            addAsset.setAccessible(true);
            addAsset.invoke(asset, mDexPath);
            mAssetManager = asset;
            mResources = new Resources(asset, getResources().getDisplayMetrics()
                    , getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //然后重写这个Activity的getResource(),getAsset()
    ,返回刚才用反射创建的实例即可。

     @Override
    public Resources getResources() {
        if (mResources == null) {
            return super.getResources();
        }
        return mResources;
    }

    @Override
    public AssetManager getAssets() {
        if (mAssetManager == null) {
            return super.getAssets();
        }
        return;

宿主Activity完整代码

package com.example.yangtianrui.viewsystem;

import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

import dalvik.system.DexClassLoader;

public class DLPluginActivity extends AppCompatActivity {

    private static final String TAG = "dlp";
    private static final File SDCARD = Environment.getExternalStorageDirectory();

    private File mDexOutPath;
    private File mLibPath;
    private ClassLoader mMyClassLoader;
    private String mDexPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dlplugin_activitiy);

        mDexOutPath = getDir("dex", MODE_PRIVATE);
        mLibPath = getDir("pluginlib", MODE_PRIVATE);
        mDexPath = SDCARD.getAbsolutePath() + "/plugin_test/plugina-debug.apk";
        mMyClassLoader = new DexClassLoader(mDexPath
                , mDexOutPath.getAbsolutePath(), mLibPath.getAbsolutePath(), getClassLoader());
        //mMyClassLoader = createDexClassLoader(mDexPath);
        loadResource();

        // 使用PackageInfo获取到类名
        PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, PackageManager.GET_ACTIVITIES);
        if (packageInfo.activities == null) {
            Log.d(TAG, "onCreate: activities are null!!!");
            return;
        }
        Log.d(TAG, "onCreate: packageInfo activities " + Arrays.toString(packageInfo.activities));

        try {
            //1.先加载到类
            // 使用ClassLoader#loadClass时,需要完成的报名和类名。
            // com.example.plugina.PluginMainActivity
            final Class clazz = mMyClassLoader.loadClass(packageInfo.activities[0].name);
            Log.d(TAG, "onCreate: get class " + clazz.toString());
            // 2. 获取构造器对象
            Log.d(TAG, "onCreate: constructors: " + Arrays.toString(clazz.getConstructors()));
            Constructor<?> constructor = clazz.getConstructor();
            if (constructor == null) {
                return;
            }
            Log.d(TAG, "onCreate: get constructor ");

            Method[] methods = clazz.getMethods();
            for (int i = 0; i < methods.length; i++) {
                if (methods[i].getName().equals("setProxy")) {
                    Log.d(TAG, "onCreate: find setProxy " + methods[i]);
                }
                Log.d(TAG, "onCreate: method " + methods[i].getName());
            }

            // 3. 初始化对象
            final Object remote = constructor.newInstance();
            // 4. 回调onCreate()
            // fixme getDeclaredMethod和getMethod区别
            final Method onCreate = clazz.getDeclaredMethod("onCreate", Bundle.class);
            onCreate.setAccessible(true);
            final Method setProxy = clazz.getMethod("setProxy", Activity.class);
            setProxy.setAccessible(true);

            setProxy.invoke(remote, this);
            onCreate.invoke(remote, savedInstanceState);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Resources mResources;
    private AssetManager mAssetManager;

    private void loadResource() {
        try {
            AssetManager asset = AssetManager.class.newInstance();
            Method addAsset = AssetManager.class.getMethod("addAssetPath", String.class);
            addAsset.setAccessible(true);
            addAsset.invoke(asset, mDexPath);
            mAssetManager = asset;
            mResources = new Resources(asset, getResources().getDisplayMetrics()
                    , getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void onContentChanged() {
        Log.d(TAG, "onContentChanged: ");
        super.onContentChanged();
    }

    @Override
    public Resources getResources() {
        if (mResources == null) {
            return super.getResources();
        }
        return mResources;
    }

    @Override
    public AssetManager getAssets() {
        if (mAssetManager == null) {
            return super.getAssets();
        }
        return mAssetManager;
    }

    private ClassLoader createDexClassLoader(String dex) {
        return new DexClassLoader(dex
                , mDexOutPath.getAbsolutePath(), mLibPath.getAbsolutePath(), getClassLoader());
    }
}

演示效果

成功加载宿主中的layout资源。