(4.6.29.5)插件化之代码加载:Dynamic-Load-Apk

一、概述

  • singwhatiwanna/dynamic-load-apk

  • DL实现原理

    • 使用DexClassLoader加载未安装apk
    • 启动Activity:使用代理模式去执行插件Activity,加载对应的生命周期
    • 访问R资源:通过反射调用AssetManager中的addAssetPath方法
  • Dl的特性

    • 开源
    • 轻量级,仅需引用 dl-ib.jar,DL的工作过程对开发者完全透明
    • 采用代理模式,容易进行扩展
    • 基本无反射调用
    • 支持 android2.x版本
    • 支持用R访问plugin资源
    • 插件安装后认可独立运行从而编译调试
    • 多种开发方式,适用于实际开发中的不同场景。前两种模式适用于plugin开发者无法获得host代码的情况,最后适用于plugin开发者能获得host代码的情况。
      • 插件不依赖宿主模式(推荐)。无调用(但仍然可以用反射调用:main-host、main-plugin
      • 插件部分依赖宿主模式(接口下沉)。host可公开部分接口供plugin调用:doi-host、doi-plugin、doi-common接口工程
      • 插件完全依赖宿主: depend_on_host、depend_on_plugin
    • 支持多进程模式,插件可以运行在单独的DL进程中(代码在lab分支)(DL 2.0的新特性)
    • 支持插件中的so库(代码在dev分支)(DL 2.0的新特性)
  • Dl开发规范

    • 目前支持Activity和FragmentActivity基类,其他类型可自行实现
    • 启动新activity的约束:启动外部activity不受限制,启动apk内部的activity有限制,首先由于apk中的activity没注册,所以不支持隐式调用,其次必须通过BaseActivity中定义的新方法startActivityByProxy和startActivityForResultByProxy,还有就是不支持LaunchMode
    • 只支持动态注册广播
    • 不支持插件中的assets
    • 插件所需要权限需要在宿主工程中声明
    • 调用context的时候,适当使用that。that是apk中activity的基类BaseActivity系列中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,由于that的动态分配特性,通过that去调用activity的成员方法,在apk安装以后仍然可以正常运行
    • 慎重使用this,因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是,当this表示的不是Context对象的时候除外,比如this表示一个由activity实现的接口
    • activity的成员方法调用问题:原则来说,需要通过that来调用成员方法,但是由于大部分常用的api已经被重写,所以仅仅是针对部分api才需要通过that去调用用。同时,apk安装以后仍然可以正常运行
    • 目前支持style和系统主题,不支持自定义主题
  • 核心库介绍

    • master分支
      • 类加载器管理 DLClassLoader
      • 生命周期代理接口 DLPlugin
      • 代理基类 DLProxyActivity、DLProxyFragmentActivity
    • dev分支
      • DLIntent 自定义Activity跳转
      • DLPluginManager插件管理
  • 注意事项

    • dl-lib包的集成
      • 插件编译的时候依赖jar包,但是打包成apk的时候不要把jar包打入
      • 更改lib工程后,重新build后,bin目录可获取新的jar包
    • 实际部署和插件工程升级
      • 插件的安全性校验
      • 插件工程需要又主项目进行更新,关闭自己的升级行为
      • 检查插件是否可用,约定协议差异过大时,建议降级安装。 目前DL也支持插件的独立安装和运行

1.1 DL对activity生命周期管理的改进

DL最开始的时候采用反射去管理activity的生命周期,这样存在一些不便,比如反射代码写起来复杂,并且过多使用反射有一定的性能开销

采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用函数反射

public interface DLPlugin {  
  
    public void onStart();  
    public void onRestart();  
    public void onActivityResult(int requestCode, int resultCode, Intent data);  
    public void onResume();  
    public void onPause();  
    public void onStop();  
    public void onDestroy();  
    public void onCreate(Bundle savedInstanceState);  
    public void setProxy(Activity proxyActivity, String dexPath);  
    public void onSaveInstanceState(Bundle outState);  
    public void onNewIntent(Intent intent);  
    public void onRestoreInstanceState(Bundle savedInstanceState);  
    public boolean onTouchEvent(MotionEvent event);  
    public boolean onKeyUp(int keyCode, KeyEvent event);  
    public void onWindowAttributesChanged(LayoutParams params);  
    public void onWindowFocusChanged(boolean hasFocus);  
    public void onBackPressed();  
}  
...  
    @Override  
    protected void onStart() {  
        mRemoteActivity.onStart();  
        super.onStart();  
    }  
  
    @Override  
    protected void onRestart() {  
        mRemoteActivity.onRestart();  
        super.onRestart();  
    }  
  
    @Override  
    protected void onResume() {  
        mRemoteActivity.onResume();  
        super.onResume();  
    }  
...

1.2 DL对类加载器的支持

为了更好地对多插件进行支持,我们提供了一个DLClassoader类,专门去管理各个插件的DexClassoader,这样,同一个插件就可以采用同一个ClassLoader去加载类从而避免多个classloader加载同一个类时所引发的类型转换错误

public class DLClassLoader extends DexClassLoader {  
    private static final String TAG = "DLClassLoader";  
  
    private static final HashMap<String, DLClassLoader> mPluginClassLoaders = new HashMap<String, DLClassLoader>();  
  
    protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {  
        super(dexPath, optimizedDirectory, libraryPath, parent);  
    }  
  
    /** 
     * return a available classloader which belongs to different apk 
     */  
    public static DLClassLoader getClassLoader(String dexPath, Context context, ClassLoader parentLoader) {  
        DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath);  
        if (dLClassLoader != null)  
            return dLClassLoader;  
  
        File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE);  
        final String dexOutputPath = dexOutputDir.getAbsolutePath();  
        dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null, parentLoader);  
        mPluginClassLoaders.put(dexPath, dLClassLoader);  
  
        return dLClassLoader;  
    }  
}  

1.3 DL对宿主(host)和插件(plugin)通信的支持

在这里插入图片描述
【图 dl 三种模式】

因为往往宿主需要和插件进行各种通信,因此DL对宿主和插件的通信做了很好的支持,目前总共有3中模式

在这里插入图片描述
【图DL对宿主(host)和插件(plugin)通信的支持】

  • main:插件不依赖宿主的模式,这是DL推荐的模式

    • host指宿主工程,plugin指插件工程
  • depend_on_interface:插件部分依赖宿主的模式,或者说插件依赖宿主提供的接口,适合能够拿到宿主的接口的情况

    • host指宿主工程,plugin指插件工程,common指接口工程
  • depend_on_host:插件完全依赖宿主的模式,适合于能够能到宿主的源代码的情况

    • host指宿主工程,plugin指插件工程

1.4 DL对插件独立运行的支持

为了便于调试,采用DL所开发的插件都可以独立运行,当然,这要分情况来说:

对于模式1(不依赖),如果插件想独立运行,只需要把external-jars下的jar包拷贝一份到插件的libs目录下即可

对于模式2(部分依赖),只需要提供一个宿主接口的默认实现即可

对于模式3(完全依赖),只需要apk打包时把所引用的宿主代码打包进去即可,具体方式可以参看sample/depend_on_host目录。

在开发过程中,应该先开启插件的独立运行功能以便于调试,等功能开发完毕后再将其插件化

1.5 DL对activity随意跳转的支持(DLIntent)和 对插件管理的支持(DLPluginManager)

这两项都属于加强功能,目前正在dev分支进行code review,大家感兴趣可以去dev分支上查看,等验证通过即merge到稳定版master分支。

DLIntent:通过DLIntent来完成activity的无约束调起

DLPluginManager:对宿主的所有插件提供综合管理功能

二、利用DL框架进行开发的步骤

2.1 引入

宿主工程中只要将dl-lib.jar加入libs即可,然后在gradle中引用

compile fileTree(dir: 'libs', include: ['*.jar'])

但是插件中则不同,因为DL插件需要用到DL库的类(),所以需要引入DL库,但是插件是最终要加载到宿主程序中的,宿主程序中也是引入了DL库的,如果常规办法导入DL库,则会有两份DL的拷贝,为了解决这个问题,我们让插件中的DL只是编译的时候用,但是不打包进apk。如何让它参与编译却不被打包进apk呢?在Android-studio中
只需要在插件工程中创建一个目录,比如external-jars,然后把dl-lib.jar和放进去,同时在gradle中追加如下代码即可:

provided files('external-jars/dl-lib.jar')

2.2 插件

  • 插件中的所有Activity 必须是继承自DLBasePluginActivity或者是DLBasePluginFragmentActivity。如果原有的为Activity,这里需要改为继承DLBasePluginActivity,如果原来为FragmentActivity,那么需要继承DLBasePluginFragmentActivity
public class MainActivity extends DLBasePluginActivity

TestFragmentActivity extends DLBasePluginFragmentActivity
  • 原有activity中所有代表context引用的this都必须改写为that

  • 插件中启动另外一个activity,不能使用startActivity(),而是使用startPluginActivity,并且intent也要变为DLIntent

DLIntent intent = new DLIntent(getPackageName(), ListActivity.class);
intent.putExtra(TYPE, item.getNavigationInfo());
startPluginActivity(intent);

2.3 host

  1. 通过Class.forName的方式获取我们需要调用的插件apk中MainActivity的class对象
  2. 就上面提到的,我们需要判断该对象继承自DLBasePluginActivity还是DLBasePluginFragmentActivity,得到对应的代理class对象
  3. 使用对应的代理class对象调起插件apk
  PluginItem item = mPluginItems.get(position);  
         Class<?> proxyCls = null;  
    
         try {  
             Class<?> cls = Class.forName(item.launcherActivityName, false,  
                     DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader()));  
             if (cls.asSubclass(DLBasePluginActivity.class) != null) {  
                 proxyCls = DLProxyActivity.class;  
             }  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
            Toast.makeText(this,  
                    "load plugin apk failed, load class " + item.launcherActivityName + " failed.",  
                    Toast.LENGTH_SHORT).show();  
        } catch (ClassCastException e) {  
            // ignored  
        } finally {  
            if (proxyCls == null) {  
                proxyCls = DLProxyFragmentActivity.class;  
            }  
            Intent intent = new Intent(this, proxyCls);  
            intent.putExtra(DLConstants.EXTRA_DEX_PATH,  
                    mPluginItems.get(position).pluginPath);  
            startActivity(intent);  
        }  

三、源码解析

3.1 核心概念

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

  • 宿主:主 App,可以加载插件,也称 Host
    • 代理组件:在宿主的 Manifest 中注册,启动插件组件时首先被启动的组件。目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)
  • 插件:插件 App,被宿主加载的 App,也称 Plugin,可以是跟普通 App 一样的 Apk 文件
    • 插件组件:插件中的组件
    • Base插件组件:插件组件的基类,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)
  • 组件:指 Android 中的Activity、Service、BroadcastReceiver、ContentProvider,目前 DL 支持Activity、Service以及动态的BroadcastReceiver

在这里插入图片描述
【DynamicLoadApk 源码解析设计图】

  • DLPluginManager
    • 插件管理模块,负责插件的加载、管理以及启动插件组件
  • Proxy
    • 代理组件模块
    • 目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)
  • Proxy Impl
    • 代理组件公用逻辑模块,与(2)中的 Proxy 不同的是,这部分并不是一个组件,而是负责构建、加载插件组件的管理器
    • 这些 Proxy Impl 通过反射得到插件组件,然后将插件与 Proxy 组件建立关联,最后调用插件组件的 onCreate 函数进行启动
  • Base Plugin
    • 插件组件的基类模块
    • 目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)

3.2 流程

在这里插入图片描述
【DynamicLoadApk 源码解析流程图】

  1. 首先通过 DLPluginManager 的 loadApk 函数加载插件,这步每个插件只需调用一次。
  2. 通过 DLPluginManager 的 startPluginActivity 函数启动代理 Activity。
  3. 代理 Activity 启动过程中构建、启动插件 Activity

3.3 DLPluginManager

3.3.1 单例模式:构建函数

  • mNativeLibDir: 插件 Native Library 拷贝到宿主中后的存放目录路径
  • mPackagesHolder: 存储已经加载过的插件信息
public class DLPluginManager {

	//key 为包名,value 为表示插件信息的DLPluginPackage,存储已经加载过的插件信息
	private final HashMap<String, DLPluginPackage> mPackagesHolder = new HashMap<String, DLPluginPackage>();
	
	private static DLPluginManager sInstance;
    private Context mContext;

	private String mNativeLibDir = null;
	
	private int mFrom = DLConstants.FROM_INTERNAL;
	private int mResult;
	
	private DLPluginManager(Context context) {
        mContext = context.getApplicationContext();
		//在私有构造函数中将mNativeLibDir变量赋值为宿主 App 应用程序数据目录下名为pluginlib子目录的全路径
        mNativeLibDir = mContext.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath();
    }

    public static DLPluginManager getInstance(Context context) {
        if (sInstance == null) {
            synchronized (DLPluginManager.class) {
                if (sInstance == null) {
                    sInstance = new DLPluginManager(context);
                }
            }
        }

        return sInstance;
    }
	
}

3.3.2 loadApk:加载插件。

  • 参数 dexPath 为插件的文件路径
  • hasSoLib 表示插件是否含有 so 库

注意:在启动插件的组件前,必须先调用loadApk函数加载插件,并且只能在宿主中调用

public class DLPluginManager {

	/**
	 * Load a apk. Before start a plugin Activity, we should do this first.<br/>
	 * NOTE : will only be called by host apk.
	 * 
	 * @param dexPath
	 */
	public DLPluginPackage loadApk(String dexPath) {
		// when loadApk is called by host apk, we assume that plugin is invoked
		// by host.
		return loadApk(dexPath, true);
	}

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

		PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
				PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
		if (packageInfo == null) {
			return null;
		}

		DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
		if (hasSoLib) {
			copySoLib(dexPath);
		}

		return pluginPackage;
	}
}

在这里插入图片描述

【DynamicLoadApk 源码解析loadapk流程图】

loadApk 函数调用 preparePluginEnv 函数加载插件,图中虚线框为 preparePluginEnv 的流程图

/**
 * prepare plugin runtime env, has DexClassLoader, Resources, and so on.
 * 
 * @param packageInfo
 * @param dexPath
 * @return
 */
private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {

    DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
    if (pluginPackage != null) {
        return pluginPackage;
    }
    DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
    AssetManager assetManager = createAssetManager(dexPath);
    Resources resources = createResources(assetManager);
    // create pluginPackage
    pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
    mPackagesHolder.put(packageInfo.packageName, pluginPackage);
    return pluginPackage;
}

public class DLPluginPackage {

    public String packageName;//插件的包名
    public String defaultActivity;//插件的 Launcher Main Activity
    public DexClassLoader classLoader;//加载插件的 ClassLoader
    public AssetManager assetManager;//加载插件资源的 AssetManager
    public Resources resources;//s利用assetManager中已经加载的资源创建的Resources,代理组件中会从这个Resources中读取资源
    public PackageInfo packageInfo;//被PackageManager解析后的插件信息
	//这些信息都会在DLPluginManager#loadApk(…)时初始化
}	
3.3.2.1 createDexClassLoader(String dexPath)

利用DexClassLoader加载插件,DexClassLoader 初始化函数如下:

//dexPath为插件的路径
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());
    return loader;
}

利用系统的DexClassLoader加载插件,DexClassLoader 初始化函数如下:

public DexClassLoader (String dexPath
	, String optimizedDirectory
	, String libraryPath
	, ClassLoader parent)
  • optimizedDirectory为宿主内dex存放路径。这里将路径设置为当前 App 应用程序数据目录下名为dex的子目录中。
  • libraryPath为 Native Library 存放的路径。这里将路径设置为mNativeLibDir属性,其在getInstance(Context)函数中已经初始化
  • parent父 ClassLoader,ClassLoader 采用双亲委托模式查找类
3.3.2.2 createAssetManager(String dexPath)

创建 AssetManager,加载插件资源。

在 Android 中,资源是通过 R.java 中的 id 来调用访问的。但是实现插件化之后,宿主是无法通过 R 文件访问插件的资源,所以这里使用反射来生成属于插件的AssetManager,并利用addAssetPath函数加载插件资源

AssetManager 的无参构造函数以及addAssetPath函数都被hide了,通过反射调用

private AssetManager createAssetManager(String dexPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, dexPath);
        return assetManager;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

}

3.3.2.3 createResources(AssetManager assetManager)

利用AssetManager中已经加载的资源创建Resources,代理组件中会从这个Resources中读取资源

private Resources createResources(AssetManager assetManager) {
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
    return resources;
}
3.3.2.4 copySoLib(String dexPath)

调用SoLibManager拷贝 so 库到 Native Library 目录

/**
 * copy .so file to pluginlib dir.
 * 
 * @param dexPath
 * @param hasSoLib
 */
private void copySoLib(String dexPath) {
    // TODO: copy so lib async will lead to bugs maybe, waiting for
    // resolved later.

    // TODO : use wait and signal is ok ? that means when copying the
    // .so files, the main thread will enter waiting status, when the
    // copy is done, send a signal to the main thread.
    // new Thread(new CopySoRunnable(dexPath)).start();

    SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);
}

3.3.3 启动Acitity

在这里插入图片描述
【DynamicLoadApk 源码解析启动Activity流程图】

  • 启动插件 Activity,会直接调用startPluginActivityForResult(…)函数
public class DLIntent extends Intent {

	private String mPluginPackage;
    private String mPluginClass;

}


/**
 * {@link #startPluginActivityForResult(Activity, DLIntent, int)}
 */
public int startPluginActivity(Context context, DLIntent dlIntent) {
    return startPluginActivityForResult(context, dlIntent, -1);
}

/**
 * @param context
 * @param dlIntent
 * @param requestCode
 * @return One of below: {@link #START_RESULT_SUCCESS}
 *         {@link #START_RESULT_NO_PKG} {@link #START_RESULT_NO_CLASS}
 *         {@link #START_RESULT_TYPE_ERROR}
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    //内部调用,原生方式启动Activity
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        performStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.START_RESULT_SUCCESS;
    }
    //拿出Intent中的packageName
    String packageName = dlIntent.getPluginPackage();
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    //在Map集合mPackagesHolder中根据packageName拿出插件信息DLPluginPackage
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    //判断插件是否存在于mPackagesHolder中
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    //根据pluginPackage得到待启动Activity全路径
    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    //通过反射加载这个待启动Activity类
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }

    // get the proxy activity class, the proxy activity will launch the
    // plugin activity.
    //得到启动Activity类的代理类
    Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
    if (activityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }

    // put extra data
    //设置dlIntent的相关参数,将代理类设置进dlIntent中,还要设置Activity全路径,插件的包名packageName
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    dlIntent.setClass(mContext, activityClass);
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}


private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    Log.d(TAG, "launch " + dlIntent.getPluginClass());
    if (context instanceof Activity) {
        ((Activity) context).startActivityForResult(dlIntent, requestCode);
    } else {
        context.startActivity(dlIntent);
    }
}

//根据pluginPackage得到待启动Activity全路径的方法
private String getPluginActivityFullPath(DLIntent dlIntent, DLPluginPackage pluginPackage) {
    String className = dlIntent.getPluginClass();
    className = (className == null ? pluginPackage.defaultActivity : className);
    if (className.startsWith(".")) {
        className = dlIntent.getPluginPackage() + className;
    }
    return className;
}

//通过反射加载待启动 插件Activity类
// zhangjie1980 重命名 loadPluginActivityClass -> loadPluginClass
private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
    Class<?> clazz = null;
    try {
        clazz = Class.forName(className, true, classLoader);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    return clazz;
}

/**
 * get the proxy activity class, the proxy activity will delegate the plugin
 * activity
 * 得到启动Activity类的代理类,内部主要是根据这个Activity继承自DLBasePluginActivity还是DLBasePluginFragmentActivity来获取相应的代理
 * @param clazz
 *            target activity's class
 * @return
 */
private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
    Class<? extends Activity> activityClass = null;
    if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
        activityClass = DLProxyActivity.class;
    } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
        activityClass = DLProxyFragmentActivity.class;
    }

    return activityClass;
}

3.3.4 启动Service

startPluginService(final Context context, final DLIntent dlIntent)

主要逻辑在函数fetchProxyServiceClass(…)中,流程与startPluginActivity(…)类似,只是换成了回调的方式,在各种条件成立后调用原生方式启动代理 Service,不再赘述

public int startPluginService(final Context context, final DLIntent dlIntent) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        context.startService(dlIntent);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
        @Override
        public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
            // TODO Auto-generated method stub
            if (result == START_RESULT_SUCCESS) {
                dlIntent.setClass(context, proxyServiceClass);
                // start代理Service
                context.startService(dlIntent);
            }
            mResult = result;
        }
    });

    return mResult;
}

public int stopPluginService(final Context context, final DLIntent dlIntent) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        context.stopService(dlIntent);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
        @Override
        public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
            // TODO Auto-generated method stub
            if (result == START_RESULT_SUCCESS) {
                dlIntent.setClass(context, proxyServiceClass);
                // stop代理Service
                context.stopService(dlIntent);
            }
            mResult = result;
        }
    });

    return mResult;
}

public int bindPluginService(final Context context, final DLIntent dlIntent, final ServiceConnection conn,
        final int flags) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        context.bindService(dlIntent, conn, flags);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
        @Override
        public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
            // TODO Auto-generated method stub
            if (result == START_RESULT_SUCCESS) {
       dlIntent.setClass(context, proxyServiceClass);
                // Bind代理Service
                context.bindService(dlIntent, conn, flags);
            }
            mResult = result;
        }
    });

    return mResult;
}

public int unBindPluginService(final Context context, DLIntent dlIntent, final ServiceConnection conn) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        context.unbindService(conn);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
        @Override
        public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
            // TODO Auto-generated method stub
            if (result == START_RESULT_SUCCESS) {
                // unBind代理Service
                context.unbindService(conn);
            }
            mResult = result;
        }
    });
    return mResult;

}

3.4 Proxy

  • DLAttachable
    • DLAttachable 是一个接口,主要作用是以统一所有不同类型的代理 Activity,如DLProxyActivity、DLProxyFragmentActivity,方便作为同一接口统一处理。
    • DLProxyActivity和DLProxyFragmentActivity都实现了这个类
  • DLServiceAttachable
    • 类似,作用是统一所有不同类型的代理 Service,实现插件Service和代理Service的绑定。
    • 目前只有DLProxyService
public interface DLAttachable {
    /**
     * when the proxy impl ( {@see DLProxyImpl#launchTargetActivity()} ) launch
     * the plugin activity , dl will call this method to attach the proxy activity
     * and pluginManager to the plugin activity. the proxy activity will load
     * the plugin's resource, so the proxy activity is a resource delegate for
     * plugin activity.
     * 抽象函数,表示将插件Activity和代理Activity绑定在一起,其中的proxyActivity参数就是指插件Activity
     * @param proxyActivity a instance of DLPlugin, {@see DLBasePluginActivity}
     *            and {@see DLBasePluginFragmentActivity}
     * @param pluginManager DLPluginManager instance, manager the plugins
     */
    public void attach(DLPlugin proxyActivity, DLPluginManager pluginManager);
}

public interface DLServiceAttachable {

    public void attach(DLServicePlugin remoteService, DLPluginManager pluginManager);
}

3.4.1 DLProxyActivity.java/DLProxyFragmentActivity.java

代理 Activity,他们是在宿主 Manifest 中注册的组件,也是启动插件 Activity 时,真正被启动的 Activity,他们的内部会完成插件 Activity 的初始化和启动。

这两个类大同小异,所以这里只分析DLProxyActivity

3.4.1.1 DLProxyActivity

代理Acitvity,它的生命周期最终都会触发插件对应接口

public class DLProxyActivity extends Activity implements DLAttachable {

    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);

	//remoteActivity是 插件Activity
	@Override
    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
        mRemoteActivity = remoteActivity;
    }
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        impl.onCreate(getIntent());
    }

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

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

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

    @Override
    public ClassLoader getClassLoader() {
        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);
    }

}
3.4.1.2 DLProxyImpl

DLProxyImpl 主要封装了插件Activity的公用逻辑,如初始化插件 Activity 并和代理 Activity 绑定、获取资源等,相当于把DLProxyActivity和DLProxyFragmentActivity的公共实现部分提出出来,核心逻辑位于下面介绍的 onCreate() 函数

  • onCreate 函数,会在代理 Activity onCreate 函数中被调用

在这里插入图片描述
【DynamicLoadApk 源码解析DlProxyImpl流程图】

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

public void onCreate(Intent intent) {

    // set the extra's class loader
    //设置intent的ClassLoader
    intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
    //获取PackageName
    mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
    //获得待启动Activity的ClassName
    mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
    Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);

    mPluginManager = DLPluginManager.getInstance(mProxyActivity);
    //获得插件信息PluginPackage
    mPluginPackage = mPluginManager.getPackage(mPackageName);
    //从PluginPackage中获取AssetManager
    mAssetManager = mPluginPackage.assetManager;
    //从PluginPackage中获取Resources
    mResources = mPluginPackage.resources;

    initializeActivityInfo();
    handleActivityInfo();
    launchTargetActivity();
}

//加载待启动插件 Activity 完成初始化流程,并通过DLPlugin和DLAttachable接口的 attach 函数实现和代理 Activity 的双向绑定。
//流程图见上图虚线框部分
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
    try {
        Class<?> localClass = getClassLoader().loadClass(mClass);
        Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
        Object instance = localConstructor.newInstance(new Object[] {});
        mPluginActivity = (DLPlugin) instance;
		
		//【此处 将plug射入proxy】
        ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
        Log.d(TAG, "instance = " + instance);
		//【此处 将proxy射入plug】
        // attach the proxy activity and plugin package to the mPluginActivity
        mPluginActivity.attach(mProxyActivity, mPluginPackage);

        Bundle bundle = new Bundle();
        bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
        mPluginActivity.onCreate(bundle);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//获得待启动插件的 ActivityInfo,其中对插件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;
                        }
                    }
                }
            }
        }

    }
}

//设置代理 Activity 的主题等信息。 
//其他的 get* 函数都是获取一些插件相关信息,会被代理 Activity 调用。 
//同样 DLServiceProxyImpl 主要封装了插件Service的公用逻辑,如初始化插件 Service 并和代理 Activity 绑定
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.
}

3.5 Plugin

  • DLPlugin
    • 是一个接口,包含Activity生命周期、触摸、菜单等抽象函数
    • DLBase*Activity 都实现了这个类,这样插件的 Activity 间接实现了此类。
    • 主要作用是统一所有不同类型的插件 Activity,如Activity、FragmentActivity,方便作为同一接口统一处理,所以这个类叫DLPluginActivity更合适
  • DLServicePlugin
    • 主要作用是统一所有不同类型的插件 Service,方便作为统一接口统一处理,目前包含Service生命周期等抽象函数
public interface DLPlugin {

	public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);

    public void onCreate(Bundle savedInstanceState);
    public void onStart();
    public void onRestart();
    public void onActivityResult(int requestCode, int resultCode, Intent data);
    public void onResume();
    public void onPause();
    public void onStop();
    public void onDestroy();
    public void onSaveInstanceState(Bundle outState);
    public void onNewIntent(Intent intent);
    public void onRestoreInstanceState(Bundle savedInstanceState);
    public boolean onTouchEvent(MotionEvent event);
    public boolean onKeyUp(int keyCode, KeyEvent event);
    public void onWindowAttributesChanged(LayoutParams params);
    public void onWindowFocusChanged(boolean hasFocus);
    public void onBackPressed();
    public boolean onCreateOptionsMenu(Menu menu);
    public boolean onOptionsItemSelected(MenuItem item);
}

public interface DLServicePlugin {

	public void attach(Service proxyService, DLPluginPackage pluginPackage);
	
    public void onCreate(); 
    public void onStart(Intent intent, int startId); 
    public int onStartCommand(Intent intent, int flags, int startId);
    public void onDestroy();
    public void onConfigurationChanged(Configuration newConfig); 
    public void onLowMemory();
    public void onTrimMemory(int level);
    public IBinder onBind(Intent intent);
    public boolean onUnbind(Intent intent);
    public void onRebind(Intent intent);
    public void onTaskRemoved(Intent rootIntent); 
}

3.5.1 DLBasePluginActivity.java/DLBasePluginFragmentActivity.java

插件 Activity 基类,插件中的Activity都要继承 DLBasePluginActivity/DLBasePluginFragmentActivity 之一(目前尚不支持 ActionBarActivity)。

主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。

DLBasePluginActivity继承自Activity,同时实现了DLPlugin接口。这两个类大同小异,所以这里只分析DLBasePluginActivity。

  • 主要变量:
    • mProxyActivity为代理 Activity,通过attach(…)函数绑定。
    • that与mProxyActivity等同,只是为了和this指针区分,表示真实的Context,这里真实指的是被代理情况下为代理 Activity,未被代理情况下等同于 this。
/**
 * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
 */
protected Activity mProxyActivity;

/**
 * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
 * 可以当作this来使用
 */
protected Activity that;
protected DLPluginManager mPluginManager;
protected DLPluginPackage mPluginPackage;
  • Attach方法,设置代理的Activity
@Override
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
    Log.d(TAG, "attach: proxyActivity= " + proxyActivity);
    mProxyActivity = (Activity) proxyActivity;
    that = mProxyActivity;
    mPluginPackage = pluginPackage;
}
  • 根据是否被代理,确定一些函数直接走父类逻辑还是代理 Activity 或是空逻辑。(此处贴部分代码)
/**
 * @param dlIntent
 * @return may be {@link #START_RESULT_SUCCESS},
 *         {@link #START_RESULT_NO_PKG}, {@link #START_RESULT_NO_CLASS},
 *         {@link #START_RESULT_TYPE_ERROR}
 */
public int startPluginActivityForResult(DLIntent dlIntent, int requestCode) {
    if (mFrom == DLConstants.FROM_EXTERNAL) {
        if (dlIntent.getPluginPackage() == null) {
            dlIntent.setPluginPackage(mPluginPackage.packageName);
        }
    }
    return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
}

public int startPluginService(DLIntent dlIntent) {
    if (mFrom == DLConstants.FROM_EXTERNAL) {
        if (dlIntent.getPluginPackage() == null) {
            dlIntent.setPluginPackage(mPluginPackage.packageName);
        }
    }
    return mPluginManager.startPluginService(that, dlIntent);
}

3.5.2 DLBasePluginService

插件 Service 基类,插件中的 Service 要继承这个基类,主要作用是根据是否被代理,确定一些函数直接走父类逻辑还是代理 Service 或是空逻辑。

> PS:截止目前这个类还是不完善的,至少和DLBasePluginActivity对比,还不支持非代理的情况(贴部分代码)

@Override
public void attach(Service proxyService, DLPluginPackage pluginPackage) {
    // TODO Auto-generated method stub
    LOG.d(TAG, TAG + " attach");
    mProxyService = proxyService;
    mPluginPackage = pluginPackage;
    that = mProxyService;
    mFrom = DLConstants.FROM_EXTERNAL;
}

protected boolean isInternalCall() {
    return mFrom == DLConstants.FROM_INTERNAL;
}

@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    LOG.d(TAG, TAG + " onBind");
    return null;
}

@Override
public void onCreate() {
    // TODO Auto-generated method stub
    LOG.d(TAG, TAG + " onCreate");
}

3.5.3 DLIntent

继承自 Intent,封装了待启动组件的 PackageName 和 ClassName

public class DLIntent extends Intent {



    private String mPluginPackage;
    private String mPluginClass;

    public DLIntent() {
        super();
    }

    public DLIntent(String pluginPackage) {
        super();
        this.mPluginPackage = pluginPackage;
    }

    public DLIntent(String pluginPackage, String pluginClass) {
        super();
        this.mPluginPackage = pluginPackage;
        this.mPluginClass = pluginClass;
    }

    public DLIntent(String pluginPackage, Class<?> clazz) {
        super();
        this.mPluginPackage = pluginPackage;
        this.mPluginClass = clazz.getName();
    }

    public String getPluginPackage() {
        return mPluginPackage;
    }

    public void setPluginPackage(String pluginPackage) {
        this.mPluginPackage = pluginPackage;
    }

    public String getPluginClass() {
        return mPluginClass;
    }

    public void setPluginClass(String pluginClass) {
        this.mPluginClass = pluginClass;
    }

    public void setPluginClass(Class<?> clazz) {
        this.mPluginClass = clazz.getName();
    }

    @Override
    public Intent putExtra(String name, Parcelable value) {
        setupExtraClassLoader(value);
        return super.putExtra(name, value);
    }

    @Override
    public Intent putExtra(String name, Serializable value) {
        setupExtraClassLoader(value);
        return super.putExtra(name, value);
    }

    private void setupExtraClassLoader(Object value) {
        ClassLoader pluginLoader = value.getClass().getClassLoader();
        DLConfigs.sPluginClassloader = pluginLoader;
        setExtrasClassLoader(pluginLoader);
    }

}

3.6 SoLibManager

调用SoLibManager拷贝 so 库到 Native Library 目录

  • copyPluginSoLib(Context context, String dexPath, String nativeLibDir)
    • 函数中以ZipFile形式加载插件,循环读取其中的文件,如果为.so结尾文件、符合当前平台 CPU 类型且尚未拷贝过最新版,则新建Runnable拷贝 so 文件。
  /**
 * copy so lib to specify directory(/data/data/host_pack_name/pluginlib)
 *
 * @param dexPath      plugin path
 * @param nativeLibDir nativeLibDir
 */
public void copyPluginSoLib(Context context, String dexPath, String nativeLibDir) {
    String cpuName = getCpuName();
    String cpuArchitect = getCpuArch(cpuName);

    sNativeLibDir = nativeLibDir;
    Log.d(TAG, "cpuArchitect: " + cpuArchitect);
    long start = System.currentTimeMillis();
    try {
        ZipFile zipFile = new ZipFile(dexPath);
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = (ZipEntry) entries.nextElement();
            if (zipEntry.isDirectory()) {
                continue;
            }
            String zipEntryName = zipEntry.getName();
            if (zipEntryName.endsWith(".so") && zipEntryName.contains(cpuArchitect)) {
                final long lastModify = zipEntry.getTime();
                if (lastModify == DLConfigs.getSoLastModifiedTime(context, zipEntryName)) {
                    // exist and no change
                    Log.d(TAG, "skip copying, the so lib is exist and not change: " + zipEntryName);
                    continue;
                }
                mSoExecutor.execute(new CopySoTask(context, zipFile, zipEntry, lastModify));
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    long end = System.currentTimeMillis();
    Log.d(TAG, "### copy so time : " + (end - start) + " ms");
}

//获取CPU类型
private String getCpuName() {
    try {
        FileReader fr = new FileReader("/proc/cpuinfo");
        BufferedReader br = new BufferedReader(fr);
        String text = br.readLine();
        br.close();
        String[] array = text.split(":\\s+", 2);
        if (array.length >= 2) {
            return array[1];
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}
//获取CPU架构
@SuppressLint("DefaultLocale")
private String getCpuArch(String cpuName) {
    String cpuArchitect = DLConstants.CPU_ARMEABI;
    if (cpuName.toLowerCase().contains("arm")) {
        cpuArchitect = DLConstants.CPU_ARMEABI;
    } else if (cpuName.toLowerCase().contains("x86")) {
        cpuArchitect = DLConstants.CPU_X86;
    } else if (cpuName.toLowerCase().contains("mips")) {
        cpuArchitect = DLConstants.CPU_MIPS;
    }

    return cpuArchitect;
}

3.7 DLUtils

这个类中大都是无用或是不该放在这里的函数,也许是大版本升级及维护人过多后对工具函数的维护不够所致

四、DL插件开发注意事项

4.1 主题

  • dl的插件必须每个activity都单独设置主题(插件的作者说的是也可以在application上设置主题),但我实际测试,即使application设置了主题也必须每个activity都单独设置主题。
  <application
      android:allowBackup="true"
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name" >
      <activity
          android:name=".SampleActivity"
          android:theme="@android:style/Theme.Holo.Light.DarkActionBar" 
          android:label="@string/app_name" >
          <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
     </activity>
 </application>
  • 注意的是 插件只能用系统主题 不能直接定义主题
不能这样
android:theme="@style/AppTheme"

只能这样
android:theme="@android:style/Theme.Light"

虽然在某些插件上可能不按照此规则也可以正确运行 ,但是我试过绝大多数多需要满足此条件

4.2 插件APK的管理后台

使用动态加载的目的,就是希望可以绕过APK的安装过程升级应用的功能,如果插件APK是打包在主项目内部的那动态加载纯粹是多次一举。更多的时候我们希望可以在线下载插件APK,并且在插件APK有新版本的时候,主项目要从服务器下载最新的插件替换本地已经存在的旧插件。为此,我们应该有一个管理后台,它大概有以下功能:

  1. 上传不同版本的插件APK,并向APP主项目提供插件APK信息查询功能和下载功能;
  2. 管理在线的插件APK,并能向不同版本号的APP主项目提供最合适的插件APK;
  3. 万一最新的插件APK出现紧急BUG,要提供旧版本回滚功能;
  4. 出于安全考虑应该对APP项目的请求信息做一些安全性校验;

4.3 插件APK合法性校验

加载外部的可执行代码,一个逃不开的问题就是要确保外部代码的安全性,我们可不希望加载一些来历不明的插件APK,因为这些插件有的时候能访问主项目的关键数据。
最简单可靠的做法就是校验插件APK的MD5值,如果插件APK的MD5与我们服务器预置的数值不同,就认为插件被改动过,弃用

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值