突然想到Android 的插件化开发,于是网上搜罗资料,初步认知demo如下: 
主要思想:利用 类加载器ClassLoader实现。

解决主要问题:一个是65K 大小问题,另外可以动态加载apk实现程序的动态更新等等。

简要思路:插件化APK的思路为:将部分代码分离出来放在另外的APK中,做成插件APK的形式,在我们的应用程序启动后,在使用时动态加载该插件APK中的内容。该思路简单来说便是将部分代码放在了另外一个独立的APK中,而不是放在我们自己的dex中。这样一方面减少了我们自己dex中方法总数,另一方面也减小了dex文件的大小,因此可以解决如上两个方面的问题。对于这个插件APK包含的类,我们可以在使用到的时候再加载进来,这便是动态加载的思路。

面临主要问题: 1. 如何生成插件APK;2. 如何加载插件APK;3. 如何使用插件APK中的内容。 当然,使用插件化动态加载其它apk会面临诸多其它问题,如适配性,某些资源可访问性,各种组件以及原生Android方法的可使用性等等。

————————————————————————————————————

类加载器 
在实现插件化APK之前,我们需要先了解一下Android中的类加载机制,作为实现动态加载的基础。 
在Android中,我们通过ClassLoader来加载应用程序运行需要的类。ClassLoader是一个抽象类,我们需要继承该类来实现具体的类加载器的行为。在Android中,ClassLoader的实现类采用了代理模型(Delegation Model)来执行类的加载。每一个ClassLoader类都有一个与之相关联的父加载器,当一个ClassLoader类尝试加载某个类时,首先会委托其父加载器加载该类。如果父加载器成功加载了该类,则不会再由该子加载器进行加载;如果父加载器未能加载成功,则再由子加载器进行类加载的动作。

在Android中,我们一般使用DexClassLoader和PathClassLoader进行类的加载。

DexClassLoader: 可以从.jar或者.apk文件中加载类;

PathClassLoader: 只能从系统内存中已安装的内容中加载类。

对于我们的插件化APK,需要使用DexClassLoader进行自定义类加载。我们看一下DexClassLoader的构造方法:

/*** Create DexClassLoader* @param dexPath String: the list of jar/apk files containing classes and resources, delimited by File.pathSeparator, which defaults to ":" on Android* @param optimizedDirectory String: directory where optimized dex files should be written; must not be null* @param librarySearchPath String: the list of directories containing native libraries, delimited by File.pathSeparator; may be null* @param parent ClassLoader: the parent class loader*/
DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

从以上可以看到,该构造方法的入参中除了指定各种加载路径外,还需要指定一个父加载器,以此实现我们以上提到的类加载代理模型。

下面结合一个类似于helloworld的简单demo来初步体验一下

  1. 实现一个简单的功能:在主Activity上TextView从插件的获取信息显示。

  2. 简要步骤: 
    ①创建我们的宿主APK; 
    ②创建一个library提供共享的接口; 
    ③创建插件APK并通过共享接口实现获取信息方法; 
    ④宿主APK中实现类加载器,实现动态加载。

————————————————————————————–

步骤1:Android Studio创建工程

其中app为我们的宿主App。

步骤2:创建library module其中实现共享的接口

public interface IPluginInterface{String getVersion();String getPluginMessage();}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

为了让插件APK引用该library定义的接口,我们将该library工程生成一个jar包。 
打包Jar: 
①可以在该module的gradle中添加

task makeJar(type:Copy){delete 'build/libs/interfacelibrary.jar'from('build/intermediates/bundles/release/')into('build/libs/')include('classes.jar')rename('classes.jar','pluginlibrary.jar')
}
makeJar.dependsOn(build)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后terminal命令行中gradlew makeJar即可在build的libs中生成jar;

②或者gradle中如下配置:

android.libraryVariants.all { variant ->def name = variant.buildType.nameif (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {return; // Skip debug builds.}def task = project.tasks.create "jar${name.capitalize()}", Jartask.dependsOn variant.javaCompiletask.from variant.javaCompile.destinationDirartifacts.add('archives', task);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

terminal中输入gradlew pluginlibrary:jarRelease 同样可以生成。 
两种方法生成的jar包中包含的资源不一样,大小也不一样,可以打开看看。

步骤3: 制作插件apk 
新建插件Pluginmodule的新module,将步骤2中的jar包拷贝到libs目录下,然后在工程中实现共享接口IPluginInterface的具体实现:

public class IPluginImpl implements IPluginInterface {@Overridepublic String getVersion() {return "Version 1.0";}@Overridepublic String getPluginMessage() {return "Hi,this is from the Plugin Module!";}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

插件部分的代码就写完了!接下来,我们需要生成一个插件APK,将该APK放在应用程序app module的SourceSet下,供app module的类加载器进行加载。为此,我们在plugin的gradle脚本中添加如下配置:

buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'applicationVariants.all { variant ->variant.outputs.each { output ->def apkName = "plugin.apk"output.outputFile = file("$rootProject.projectDir/app/src/main/assets/plugin/" + apkName)}}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

根目录terminal中键入gradlew pluginnodule:assembleRelease将生成的插件apk放在宿主app的assets目录下。 
ok插件apk制作完毕。

步骤4 宿主app module中实现类加载并定义接口实现调用插件中的方法

①首先需要将assets中的apk拷贝到存储中 
②从存储中加载该apk 
③实现插件管理类来调用相应的方法实现逻辑

==>将APK复制到SD卡的代码:

*** 将插件APK保存至SD卡* @param pluginName    插件APK的名称*/
private boolean savePluginApkToStorage(String pluginName) {String pluginApkPath = this.getPlguinApkDirectory() + pluginName;File plugApkFile = new File(pluginApkPath);if (plugApkFile.exists()) {try {plugApkFile.delete();} catch (Throwable e) {}}BufferedInputStream inStream = null;BufferedOutputStream outStream = null;try {InputStream stream = TestApplication.getInstance().getAssets().open("plugin/" + pluginName);inStream = new BufferedInputStream(stream);outStream = new BufferedOutputStream(new FileOutputStream(pluginApkPath));final int BUF_SIZE = 4096;byte[] buf = new byte[BUF_SIZE];while(true) {int readCount = inStream.read(buf, 0, BUF_SIZE);if (readCount == -1) {break;}outStream.write(buf,0, readCount);}} catch(Exception e) {return false;} finally {if (inStream != null) {try {inStream.close();} catch (IOException e) {}inStream = null;}if (outStream != null) {try {outStream.close();} catch (IOException e) {}outStream = null;}}return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

==>创建DexClassLoader:

DexClassLoader classLoader = null;
try {String apkPath = getPlguinApkDirectory() + pluginName;File dexOutputDir = TestApplication.getInstance().getDir("dex", 0);String dexOutputDirPath = dexOutputDir.getAbsolutePath();ClassLoader cl = TestApplication.getInstance().getClassLoader();classLoader = new DexClassLoader(apkPath, dexOutputDirPath, null, cl);
} catch(Throwable e) {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里我们使用如上提到的DexClassLoader的构造方法,其中第一个参数是我们插件APK的路径,最后一个参数是Application生成的父ClassLoader。

==>动态加载:

/*** 加载指定名称的类* @param className    类名(包含包名)*/
public Object newInstance(String className) {if (mDexClassLoader == null) {return null;}try {Class<?> clazz = mDexClassLoader.loadClass(className);Object instance = clazz.newInstance();return instance;} catch (Exception e) {Log.e(Const.LOG, "newInstance className = " + className + " failed" + " exception = " + e.getMessage());}return null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

==>具体使用时在app的Application类中实现插件apk的加载,然后再MainActivity中中通过PluginManager类来调用。

        text_version=(TextView)findViewById(R.id.text_version);text_info=(TextView)findViewById(R.id.text_info);PluginManager pluginManager=PluginManager.getInstance();IPluginInterface pluginTool=pluginManager.createTestPluginInstance();text_version.setText(pluginTool.getVersion());text_info.setText(pluginTool.getPluginMessage());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

结果如下: 

源码地址:https://github.com/Oliver-C-SH/AndroidPluginTest 
======================================================

关于Android插件化开发的的框架思路介绍:

  1. VirtualAPK:滴滴 Android 插件化的实践之路: 
    http://geek.csdn.net/news/detail/130917

一、前言 
在 Android 插件化技术日新月异的今天,开发并落地一款插件化框架到底是简单还是困难,这个问题不同人会有不同的答案。但是我相信,完成一个插件化框架的 Demo 并不是多难的事儿,然而要开发一款完善的插件化框架却并非易事,尤其在国内,各大 ROM 厂商都对 Android 系统做了一定程度的定制,这更进一步加剧了 Android 本身的碎片化问题。

滴滴出行在插件化上的探索起步较晚,由于业务发展较快,迭代占据了大量的时间,这使得我们在2016年才开始研究这方面的技术。经过半年的开发、测试、适配和线上验证,今天,我们正式推出一款较为完善的插件化框架——VirtualAPK。之所以现在推出,是因为 VirtualAPK 在内部已经得到了很好的验证,我们在迭代过程中不断地做机型适配和细节特性的支持,目前已达到一个非常稳定的状况,足以支撑滴滴部分乃至全部业务的动态发版需求。目前滴滴出行最新版本(v5.0.4)上,小巴和接送机业务均为插件,大家可以去体验。

二、插件化的现状 
到目前为止,业界已经有很多优秀的开源项目,比如早期的基于静态代理思想的 DynamicLoadApk,随后的基于占坑思想的 DynamicApk、Small,还有360手机助手的 DroidPlugin。它们都是优秀的开源项目,很大程度上促进了国内插件化技术的发展。

尽管有如此多的优秀框架存在,但是兼容性问题仍然是制约插件化发展的一大难题。一款插件化框架,也许可以在一款手机上完美运行,但是在数以千万的设备上却总是容易存在这样那样的兼容性问题。我相信上线过插件化的工程师应该深有体会。滴滴为什么还要自研一款新的插件化框架?因为我们需要一款功能完备、兼容性优秀、适用于滴滴业务的插件化框架,目前市面上的开源不能满足我们的需求,所以必须重新造轮子,于是 VirtualAPK 诞生了。

三、VirtualAPK 的诞生 
VirtualAPK 是滴滴出行自研的一款优秀的插件化框架,主要有如下几个特性。

1.功能完备 
支持几乎所有的 Android 特性; 
四大组件方面:四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。

Activity:支持显示和隐式调用,支持 Activity 的 theme 和 LaunchMode,支持透明主题; 
Service:支持显示和隐式调用,支持 Service 的 start、stop、bind 和 unbind,并支持跨进程 bind 插件中的 Service; 
Receiver:支持静态注册和动态注册的 Receiver; 
ContentProvider:支持 provider的所有操作,包括 CRUD 和 call 方法等,支持跨进程访问插件中的 Provider。 
自定义View:支持自定义 View,支持自定义属性和 style,支持动画;

PendingIntent:支持 PendingIntent 以及和其相关的 Alarm、Notification 和AppWidget; 
支持插件 Application 以及插件 manifest 中的 meta-data; 
支持插件中的so。 
2. 优秀的兼容性 
兼容市面上几乎所有的 Android 手机,这一点已经在滴滴出行客户端中得到验证; 
资源方面适配小米、Vivo、Nubia 等,对未知机型采用自适应适配方案; 
极少的 Binder Hook,目前仅仅 hook 了两个 Binder:AMS 和 IContentProvider,Hook过程做了充分的兼容性适配; 
插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。 
3. 入侵性极低 
插件开发等同于原生开发,四大组件无需继承特定的基类; 
精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖; 
插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。 
四、VirtualAPK 的工作过程 
VirtualAPK 对插件没有额外的约束,原生的 apk 即可作为插件。插件工程编译生成 apk 后,即可通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象。如下图所示,通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。

VirtualAPK 
1. VirtualAPK 的运行形态 
我们计划赋予 VirtualAPK 两种工作形态,耦合形态和独立形态。目前 VirtualAPK 对耦合形态已经有了很好的支持,接下来将计划支持独立形态。

耦合形态:插件对宿主可以有代码或者资源的依赖,也可以没有依赖。这种模式下,插件中的类不能和宿主重复,资源 id 也不能和宿主冲突。这是 VirtualAPK 的默认形态,也是适用于大多数业务的形态。 
独立形态:插件对宿主没有代码或者资源的依赖。这种模式下,插件和宿主没有任何关系,所以插件中的类和资源均可以和宿主重复。这种形态的主要作用是用于运行一些第三方 apk。 
2. 如何使用 
第一步: 初始化插件引擎

@Override
protected void attachBaseContext(Context base) {super.attachBaseContext(base);PluginManager.getInstance(base).init();
}
第二步:加载插件public class PluginManager {public void loadPlugin(final File apk);public void loadPlugin(final String moduleCode);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们对上述加载过程进行了一些封装,通过如下方式即可异步地去加载一个插件。

// 示例:启动插件中的Activity
DownloadManager downloadManager = DownloadManager.getInstance(this);
downloadManager.loadModule("com.ryg.test", true, this, new ILoadListener() {@Overridepublic void onLoadEnd(int resultCode) {if (resultCode == ILoadListener.LOAD_SUCCESS) {Intent intent = new Intent();intent.setClassName("com.ryg.test", "com.ryg.test.MainActivity");startActivity(intent);} else {// todo load plugin failed}}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

当插件入口被调用后,插件的后续逻辑均不需要宿主干预,均走原生的 Android 流程。比如,在插件内部,如下代码将正确执行:

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_book_manager);LinearLayout holder = (LinearLayout)findViewById(R.id.holder);TextView imei = (TextView)findViewById(R.id.imei);imei.setText(IDUtil.getUUID(this));// bind service in pluginIntent service = new Intent(this, BookManagerService.class);bindService(service, mConnection, Context.BIND_AUTO_CREATE);// start activity in pluginIntent intent = new Intent(this, TCPClientActivity.class);startActivity(intent);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

五、探究原理 
1. 基本原理 
合并宿主和插件的ClassLoader:需要注意的是,插件中的类不可以和宿主重复; 
合并插件和宿主的资源:重设插件资源的packageId,将插件资源和宿主资源合并; 
去除插件包对宿主的引用:构建时通过 Gradle 插件去除插件对宿主的代码以及资源的引用。 
2. 四大组件的实现原理 
VirtualAPK 
Activity:采用宿主 manifest 中占坑的方式来绕过系统校验,然后再加载真正的 Activity; 
Service:动态代理 AMS,拦截 Service 相关的请求,将其中转给一个虚拟空间(Matrix)去处理,Matrix 会接管系统的所有操作; 
Receiver:将插件中静态注册的 Receiver 重新注册一遍; 
ContentProvider:动态代理 IContentProvider,拦截 Provider 相关的请求,将其中转给一个虚拟空间(Matrix)去处理,Matrix 会接管系统的所有操作。 
以下是 VirtualAPK 的整体结构图。

VirtualAPK 
六、填坑之路 
在实践中我们遇到了很多很多的问题,比如机型适配、API 版本适配、Binder Hook 的稳定性保证等问题,这里拿一个典型的资源适配问题来说明。

其实这是一个很无奈的问题,由于国内各大 ROM 厂商喜欢深度定制 Android 系统,所以就出现了这种适配问题。

正常情况下我们通过如下代码去创建插件的 Resources 对象:

Resources newResources = new Resources(assetManager, 
hostResources.getDisplayMetrics(), hostResources.getConfiguration()); 
然后在 Vivo 手机上,竟然出现了如下的类型转换错误,看起来是 Vivo 自己派生了 Resources 的子类。

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sdu.didi.psnger/com.didi.virtualapk.core.A$1}: java.lang.ClassCastException: android.content.res.Resources cannot be cast to android.content.res.VivoResourcesat android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2196)at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)at android.app.ActivityThread.access$800(ActivityThread.java:140)at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1202)at android.os.Handler.dispatchMessage(Handler.java:102)at android.os.Looper.loop(Looper.java:136)at android.app.ActivityThread.main(ActivityThread.java:5143)at java.lang.reflect.Method.invokeNative(Native Method)at java.lang.reflect.Method.invoke(Method.java:515)at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassCastException: android.content.res.Resources cannot be cast to android.content.res.VivoResourcesat android.app.ResourcesManager.getTopLevelResources(ResourcesManager.java:236)at android.app.ContextImpl.<init>(ContextImpl.java:2057)at android.app.ContextImpl.createActivityContext(ContextImpl.java:2008)at android.app.ActivityThread.createBaseContextForActivity(ActivityThread.java:2207)at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)... 11 more
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

于是反编译了下 Vivo 的 Framework 代码,果不其然,在如下代码中进行了类型转换,所以在加载插件资源的时候就报错了。

@VivoHook(hookType = VivoHookType.NEW_METHOD)public Resources getTopLevelResources(String pkgName, String resDir, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {Resources resources = getTopLevelResources(resDir, displayId, overrideConfiguration, compatInfo, token);if (resources != null) {((VivoResources) resources).init(pkgName);}return resources;}```
为了解决这个问题,我们分析了 VivoResources 的代码实现,然后在创建插件资源的时候,采用了如下的代码。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
private static final class VivoResourcesCompat {private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {Class resourcesClazz = Class.forName("android.content.res.VivoResources");Resources newResources = (Resources)ReflectUtil.invokeConstructor(resourcesClazz,new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});return newResources;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

除了 Vivo 以外,有类似问题的还有 MIUI、Nubia 以及其他不知名的机型。而且在 Vivo 手机上,除了类型转换错误的问题,还有其他很坑的问题。

事实上我们还处理了很多其他的坑,这里无法一一说明,所以说如何保证插件化的稳定性是一件很有技术挑战的事情。

七、一些暂时不支持的特性 
由于种种原因,VirtualAPK 目前未能支持所有的 Android 的特性,已知的有如下几点:

不支持 Activity 的部分属性,比如 process、configChanges 等; 
暂不支持 overridePendingTransition(int enterAnim, int exitAnim) 这种形式的转场动画; 
插件中弹通知,不能使用插件中的资源,比如图片。 
八、开源计划 
我们的目标是打造一款功能完备的插件化框架,使得各个业务线都能以插件的形式集成,从而实现 Android App 的热更新能力。

目前 VirtualAPK 还有一些特性需要进一步完善,待完善后,将会进行开源计划。我们期望 VirtualAPK 开源后,可以让其他 App 能够无缝集成,无需考虑细节实现和兼容性问题即可轻松拥有热更新能力。

2.dynamic-load-apk: 
https://github.com/singwhatiwanna/dynamic-load-apk: 
DL : Apk动态加载框架

Introduction:

APK动态加载框架(DL)解析

DL 2.0的新特性

支持多进程模式,插件可以运行在单独的DL进程中(代码在lab分支)

支持插件中的so库(代码在dev分支)

DL支持的功能

plugin无需安装即可由宿主调起。 
支持用R访问plugin资源 
plugin支持Activity和FragmentActivity(未来还将支持其他组件) 
基本无反射调用 
插件安装后仍可独立运行从而便于调试 
支持3种plugin对host的调用模式:

(1)无调用(但仍然可以用反射调用)。

(2)部分调用,host可公开部分接口供plugin调用。 这前两种模式适用于plugin开发者无法获得host代码的情况。

(3)完全调用,plugin可以完全调用host内容。这种模式适用于plugin开发者能获得host代码的情况。

只需引入DL的一个jar包即可高效开发插件,DL的工作过程对开发者完全透明

支持android2.x版本 
DL框架原理

动态加载主要有两个需要解决的复杂问题:资源的访问和activity生命周期的管理,除此之外,还有很多坑爹的小问题,而DL框架很好地解决了这些问题。需要说明的一点是,我们不可能调起任何一个未安装的apk,这在技术上是很难实现的,我们调起的apk必须受某种规范的约束,只有在这种约束下开发的apk,我们才能将其调起。

资源管理

我们知道,宿主程序调起未安装的apk,一个很大的问题就是资源如何访问,具体来说就是,凡是以R开头的资源都不能访问了,因为宿主程序中并没有apk中的资源,所以通过R来加载资源是行不通的,程序会报错:无法找到某某id所对应的资源。针对这个问题,有人提出了将apk中的资源在宿主程序中也copy一份,这虽然能解决问题,可以一听起来就很奇怪,首先这样会持有两份资源,会增加宿主程序包的大小,其次,没发布一个插件都需要将资源copy到宿主程序中,这样就意味着每发布一个插件都要更新一下宿主程序,这和插件化的思想是相悖的,插件化的目的就是要减小宿主程序apk包的大小同时降低宿主程序的更新频率并做到自由装载模块。所以这种方法并不可行。还有人提供了一种方式:将apk中的资源解压出来,然后通过文件流去读取资源,这样做理论上是可行的,但是实际操作起来还是有很大难度的,首先不同资源有不同的文件流格式,比如图片、xml等,还有就是针对不同设备加载的资源可能是不一样的,如果选择合适的资源也是一个需要解决的问题,基于这两点,这种方法不建议使用,因为它实现起来有难度。下面说说本文所采用的方法。

我们知道,activity的工作主要是由ContextImpl来完成的, 它在activity中是一个叫做mBase的成员变量。注意到Context中有如下两个抽象方法,看起来是和资源有关的,实际上context就是通过它们来获取资源的,这两个抽象方法的真正实现在ContextImpl中。也即是说,只要我们自己实现这两个方法,就可以解决资源问题了。

/** 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();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

下面看一下如何实现这两个方法 首先要加载apk中的资源:

protected void loadResources() {  try {  AssetManager assetManager = AssetManager.class.newInstance();  Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);  addAssetPath.invoke(assetManager, mDexPath);  mAssetManager = assetManager;  } catch (Exception e) {  e.printStackTrace();  }  Resources superRes = super.getResources();  mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),  superRes.getConfiguration());  mTheme = mResources.newTheme();  mTheme.setTo(super.getTheme());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

说明:加载的方法是通过反射,通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中,由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。

/** * Add an additional set of assets to the asset manager.  This can be * either a directory or ZIP file.  Not for use by applications.  Returns * the cookie of the added asset, or 0 on failure. * {@hide} */
public final int addAssetPath(String path) {  int res = addAssetPathNative(path);  return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其次是要实现那两个抽象方法

@Override
public AssetManager getAssets() {  return mAssetManager == null ? super.getAssets() : mAssetManager;
}  @Override
public Resources getResources() {  return mResources == null ? super.getResources() : mResources;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

okay,问题搞定。这样一来,在apk中就可以通过R来访问资源了。

activity生命周期的管理

为什么会有这个问题,其实很好理解,apk被宿主程序调起以后,apk中的activity其实就是一个普通的对象,不具有activity的性质,因为系统启动activity是要做很多初始化工作的,而我们在应用层通过反射去启动activity是很难完成系统所做的初始化工作的,所以activity的大部分特性都无法使用包括activity的生命周期管理,这就需要我们自己去管理。谈到activity生命周期,其实就是那几个常见的方法:onCreate、onStart、onResume、onPause等,由于apk中的activity不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),所以这几个生命周期的方法系统就不会去自动调用了。针对此类问题,采用Fragment是一个不错的方法,Fragment从3.0引入,通过support-v4包,可以兼容3.0以下的android版本。Fragment既有类似于Activity的生命周期,又有类似于View的界面,将Fragment加入到Activity中,activity会自动管理Fragment的生命周期,通过第一篇文章我们知道,apk中的activity是通过宿主程序中的代理activity启动的,将Fragment加入到代理activity内部,其生命周期将完全由代理activity来管理,但是采用这种方法,就要求apk尽量采用Fragment来实现,还有就是在做页面跳转的时候有点麻烦,当然关于Fragment相关的内容我将在后面再做研究。

大家知道,DL最开始的时候采用反射去管理activity的生命周期,这样存在一些不便,比如反射代码写起来复杂,并且过多使用反射有一定的性能开销。针对这个问题,我们采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件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();...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在代理类DLProxyActivity中的实现

...@Overrideprotected void onStart() {mRemoteActivity.onStart();super.onStart();}@Overrideprotected void onRestart() {mRemoteActivity.onRestart();super.onRestart();}@Overrideprotected void onResume() {mRemoteActivity.onResume();super.onResume();}@Overrideprotected void onPause() {mRemoteActivity.onPause();super.onPause();}@Overrideprotected void onStop() {mRemoteActivity.onStop();super.onStop();}
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

插件apk的开发规范

文章开头提到,要想成为一个插件apk,是要满足一定条件的,如下是采用本文机制开发插件apk所需要遵循的规范:

慎用this(接口除外):因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是如果this表示的是一个接口而不是context,比如activity实现了而一个接口,那么this继续有效。

使用that:既然this不能用,那就用that,that是apk中activity的基类BaseActivity中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,anyway,that is better than this。

activity的成员方法调用问题:原则来说,需要通过that来调用成员方法,但是由于大部分常用的api已经被重写,所以仅仅是针对部分api才需要通过that去调用用。同时,apk安装以后仍然可以正常运行。

启动新activity的约束:启动外部activity不受限制,启动apk内部的activity有限制,首先由于apk中的activity没注册,所以不支持隐式调用,其次必须通过BaseActivity中定义的新方法startActivityByProxy和startActivityForResultByProxy,还有就是不支持LaunchMode。

目前暂不支持Service、BroadcastReceiver等需要注册才能使用的组件,但广播可以采用代码动态注册。

DLIntent的定义,通过自定义的intent,来完成activity的无约束调起 
UI Bus 
一些android特性的支持

首先宿主程序运行后,会把位于/mnt/sdcard/DynamicLoadHost目录下的所有apk都加载进来,然后点击列表就可以调起对应的apk,本文中的demo和第一篇文章中的demo看起来差不多,实际是有区别的,区别有两点:activity具有生命周期、加载资源可以用R,具体的代码实现请大家参见源码。


-[1]: http://lib.csdn.net/article/android/54883 
-[2]: https://github.com/singwhatiwanna/dynamic-load-apk 
-[3]: http://geek.csdn.net/news/detail/130917

Android 插件化学习相关推荐

  1. Android插件化学习之路(一)之动态加载综述

    前段时间,公司项目完成了插件化的开发,自己也因此学习了很多Android插件化的知识,于是想把这些内容记录下来,本次带来Android插件化的第一篇:动态加载综述 Android插件化学习之路(一)之 ...

  2. Android 插件化学习 加载apk并调用类的函数

    项目地址 https://github.com/979451341/PlugStudy/tree/master/LoadSimpleClass 知识点在于通过加载apk,复制成一个个FIle,然后通过 ...

  3. Android 插件化原理学习 —— Hook 机制之动态代理

    前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...

  4. 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  5. Android 插件化总结

    2019独角兽企业重金招聘Python工程师标准>>> 1.Android中插件开发篇总结和概述 2.Android组件化和插件化开发 3.携程Android App插件化和动态加载 ...

  6. 《Android插件化技术——原理篇》

    | 导语 插件化技术最早从2012年诞生至今,已经走过了5个年头.从最初只支持Activity的动态加载发展到可以完全模拟app运行时的沙箱系统,各种开源项目层出不穷,在此挑选了几个代表性的框架,总结 ...

  7. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  8. [Android 插件化(二)] DroidPlugin 用法

    1 简介 关于Android插件化可以查看我的前一篇博客:  [Android 插件化(一)] DynamicLoadApk的用法 本篇介绍第二种实现插件化的框架,360公司出品的DroidPlugi ...

  9. [Android 插件化(一)] DynamicLoadApk的用法

    1 简介 Android大型项目中为了减小apk的体积,可以采用插件化的方法,即一些不常用的功能独立成插件,当用户需要的使用的时候再从服务器上下载回来,动态加载.这样就避免了为了满足所有用户需求而把功 ...

最新文章

  1. 【网摘】检测 iframe 是否加载完成
  2. SQLi LABS Less 17 报错注入
  3. 自顶向下彻底理解 Java 中的 volatile 关键字
  4. UVA - 1347
  5. HTML:H5新增表单type属性
  6. Java疯狂讲义读书笔记第一章
  7. Conficker病毒新变种卷土重来 可关闭杀毒软件
  8. linux制作flash软件,Linux 下的三款 Flash 独立播放器
  9. C语言开发环境搭建过程
  10. 哪有什么太迟,世界多的是大器晚成
  11. 计算机学后感作文400,考试后的感想作文400字(精选10篇)
  12. Spring Boot application properties或application yml相关配置
  13. 学籍信息管理系统-------具体设计
  14. MongoDB 运行 service mongod start 后服务没有启动成功
  15. 2020牛客暑期多校训练营(第八场)题解
  16. ios图像和图形最佳实践(三)
  17. 数字电路 第四章 组合逻辑电路
  18. 阿里云MVP行走玄奘之路
  19. TC SRM590 p1000
  20. windows中手工调整活动路由表的简单方法

热门文章

  1. 科目三考试项目分类评判标准
  2. 用vb脚本语言找出c盘所有文件及其子文件中后缀名为.txt的文档,2012年3月计算机二级VB练习题及答案:文件...
  3. matlab绘制星座图,怎么弄星座图:systemview 信号星座图怎么画
  4. python爬虫——智联招聘(上)
  5. ABAP SY标签一览表
  6. shell命令使用sed从JSON中提取指定的值
  7. elf文件反编译C语言,图文并茂,讲透C语言静态链接,ELF文件篇
  8. mars3d与echart图表结合使用
  9. Pandas 中 SettingwithCopyWarning 的原理和解决方案
  10. npm ERR! fatal: unable to access ‘https://github.com/adobe-webplatform/eve.git/‘: OpenSSL SSL_read: