dynamic-load-apk插件原理整理

  因为当前项目功能越来越多,编译速度越来越慢(公司电脑配置也挺差的...),并且方法数已超出65535的限制了,虽然通过multidex暂时解决了,但是这并不是一个好的解决方式。所以通过插件来加快编译速度以及解决方法数的限制,算是一个越来越重要的任务了,工作中还有很多新需求,所以趁放假的2天研究了下现在比较流行的插件框架dynamic-load-apk,并整理了下。

框架github地址:https://github.com/singwhatiwanna/dynamic-load-apk

lib module的svn地址:https://github.com/singwhatiwanna/dynamic-load-apk/trunk/DynamicLoadApk/lib

一、加载apk总流程:

//插件文件
File plugin = new File(apkPath);
PluginItem item = new PluginItem();
//插件文件路径
item.pluginPath = plugin.getAbsolutePath();
//PackageInfo = PackageManager.getPackageArchiveInfo
item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
//launcherActivity
if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
   item.launcherActivityName = item.packageInfo.activities[0].name;
}
//launcherService
if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
    item.launcherServiceName = item.packageInfo.services[0].name;
}
//加载apk信息
DLPluginManager.getInstance(this).loadApk(item.pluginPath);

二、loadApk信息过程:
1、createDexClassLoader:

private DexClassLoader createDexClassLoader(String dexPath) {
    dexOutputPath = mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();
    DexClassLoader loader = new DexClassLoader(dexPath,
            dexOutputPath,  //getDir("dex", Context.MODE_PRIVATE)
            mNativeLibDir,  //optimizedDirectory=getDir("pluginlib", Context.MODE_PRIVATE)
            mContext.getClassLoader());  //host.Appliceation.getClassLoader()
    return loader;
}

2、createAssetManager:

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

后面会重写DLProxyActivity的getAssets()方法,返回此处生成的AssetManager,从而实现从插件apk加载资源:

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

3、createResources:

private Resources createResources(AssetManager assetManager) {
    //通过刚创建的assetManager以及宿主程序的Resources创建Plugin的Resources
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(assetManager,
            superRes.getDisplayMetrics(),
            superRes.getConfiguration());
    return resources;
}

后面会重写DLProxyActivity的getResources()方法,返回此处生成的Resources,从而实现从插件apk加载资源:

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

4、创建pluginPackage并通过插件的packageName保存插件信息:
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);

5、copySoLib(拷贝so文件到应用的pluginlib目录下):
SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);

三、调用插件:
1、要向插件Intent传递可序列化对象,必须通过DLIntent,设置Bundle的ClassLoader:

@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); //设置Bundle的ClassLoader
}

2、startPluginActivity:
插件內部的activity之间相互调用,需要使用此方法。

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        performStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.START_RESULT_SUCCESS;
    }
    String packageName = dlIntent.getPluginPackage();
    //验证intent的包名
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    //检测插件是否加载
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    //要调用的插件Activity的class完整路径
    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    //Class.forName
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }
    //获取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity
    Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);
    if (proxyActivityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }
    //put extra data
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    dlIntent.setClass(mContext, proxyActivityClass);
    //通过context启动宿主Activity
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}

四、Activity生命周期的管理:
插件apk中的activity其实就是一个普通的对象,不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),不具有activity的性质,因为系统启动activity是要做很多初始化工作的,而我们在应用层通过反射去启动activity是很难完成系统所做的初始化工作的,所以activity的大部分特性都无法使用包括activity的生命周期管理,这就需要我们自己去管理。
DL采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可。

public interface DLPlugin {
    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 attach(Activity proxyActivity, DLPluginPackage pluginPackage);
    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);
}

DLBasePluginActivity的部分实现:

public class DLBasePluginActivity extends Activity implements DLPlugin {
    /**
     * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
     */
    protected Activity mProxyActivity;
    /**
     * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
     * 替代this来使用(应为this指向的是插件中的Activity,已经不是常规意义上的activity,所以this是没有意义的)
     * 如果是DLPlugin中已经覆盖的Activity的方法,就不需使用that了,直接调用this即可
     */
    protected Activity that;
    protected DLPluginManager mPluginManager;
    protected DLPluginPackage mPluginPackage;
    protected int mFrom = DLConstants.FROM_INTERNAL;
    @Override
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
        mProxyActivity = (Activity) proxyActivity;
        that = mProxyActivity;
        mPluginPackage = pluginPackage;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
        }
        if (mFrom == DLConstants.FROM_INTERNAL) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
            that = mProxyActivity;
        }
        mPluginManager = DLPluginManager.getInstance(that);
    }
    @Override
    public void setContentView(View view) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            super.setContentView(view);
        } else {
            mProxyActivity.setContentView(view);
        }
    }
    ......
}

在代理类DLProxyActivity中的实现:

public class DLProxyActivity extends Activity implements DLAttachable {
    protected DLPlugin mRemoteActivity;
    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;
    }
    @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();
    }
    ......
}

总结:

插件主要的2个问题就是资源加载以及Activity生命周期的管理。

资源加载:

通过反射调用AssetManager的addAssetPath方法,我们可以将一个插件apk中的资源加载到AssetManager中,然后再通过AssetManager来创建一个新的Resources对象,然后就可以通过这个Resources对象来访问插件apk中的资源了。

Activity生命周期管理:

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

另外,一个需要注意的地方:

插件项目引用 android-support-v4.jar、lib.jar等libs,生成apk时不能将这些打包到apk,只在编译时引用,只有host项目里才编译并打包,保证host以及插件中的代码只有一份。

在studio里面使用provided而非compile:

dependencies {
  provided files(‘provide-jars/android-support-v4.jar‘)
  provided files(‘provide-jars/lib.jar‘)
}

时间: 2024-11-25 13:57:13

dynamic-load-apk插件原理整理的相关文章

Dynamic load data by scrollview

The  demo generate from 北京尚学堂 package com.example.scrollview; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBar; import android.support.v4.app.Fragment; import android.os.Bundle; import android.view.LayoutInflat

MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)(转)

在介绍MyBATIS插件原理前我们需要先学习一下一些基础的知识,否则我们是很难理解MyBATIS的运行原理和插件原理的. MyBATIS最主要的是反射和动态代理技术,让我们首先先熟悉它们. 1:Java反射技术 在Java中反射技术已经大行其道,通过不断的优化性能得到了巨大的提高,而反射技术使得Java的可配置性大大提高.让我们来写一个服务打印hello + 姓名. import java.lang.reflect.InvocationTargetException; import java.l

如何编写一个WebPack的插件原理及实践

_ 阅读目录 一:webpack插件的基本原理 二:理解 Compiler对象 和 Compilation 对象 三:插件中常用的API 四:编写插件实战 回到顶部 一:webpack插件的基本原理 webpack构建工具大家应该不陌生了,那么下面我们来简单的了解下什么是webpack的插件.比如我现在写了一个插件叫 "kongzhi-plugin" 这个插件.那么这个插件在处理webpack编译过程中会处理一些特定的任务. 比如我们现在在webpack.config.js 中引入了一

apk签名原理

apk签名原理 APK签名机制原理详解https://blog.csdn.net/zwjemperor/article/details/80877203 apk签名原理及实现https://blog.csdn.net/jcgu/article/details/12745363 Apk去掉签名以及重新签名的方法https://blog.csdn.net/s13383754499/article/details/84108475 原文地址:https://www.cnblogs.com/111tes

Android中微信抢红包插件原理解析和开发实现

一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

Mybatis之插件原理

鲁春利的工作笔记,好记性不如烂笔头 转载自:深入浅出Mybatis-插件原理 Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件. 代理链的生成 Mybatis支持对Executor.StatementHandler.PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对

Eclipse dynamic web project 插件

下载了Eclipse Oxygen   发现没有Dynamic web  Project 首先我们先了解下Dynamic  Web Project  If you want to create a content-based Web application that does not contain any dynamic content (such as servlets, JSP files, filters, and associated metadata) you might prefe

Maven详解(七)------ 创建Web工程以及插件原理

1.什么是 Maven 插件? 上一篇博客我们讲了 Maven 的生命周期,我们知道 Maven 的核心是生命周期,生命周期指定了 Maven 命令执行的流程顺序.但是真正实现流程的工程是由插件来完成的. 我们也可以说 Maven 是一个执行插件的框架,每一个任务实际上都是有插件来完成.进一步说每个任务对应了一个插件目标(goal),每个插件会有一个或者多个目标,例如maven-compiler-plugin的compile目标用来编译位于src/main/java/目录下的主源码,testCo

dataTables 插件学习整理

在项目中使用了dataTables 插件,学习整理一下. dataTables 的官方中文网站 http://www.datatables.club 引入文件: 所有的都要引入 jq文件 1. dataTables 的样式 <link rel="stylesheet" href="jquery.dataTables.css"> <script src="jquery.dataTable.js"> 2. bootstrap