magicbox
一款热修复和插件技术集一身 app,下载地址
演示效果
- 目前里面有 10 款免安装的插件软件:
1.手电筒
2.wifi 连接
3.wifi 密码查看
4.火车票余票查询
5.计算器
6.俄罗斯方块
7.csdn 阅读
8.拨号声识别
9.hash-e
10.二维码
特点
- 依赖 devlibrary 开发插件,写出的 app 既可以独立运行,也可以作为插件运行
热修复
- 代码动态修复 原理
Android Support
Android version | Status |
---|---|
Android 6.0 | tested |
Android 5.0 | tested |
Android < 5.0 | tested |
详情请看:online.magicbox.bugfix.BundlePathLoader
- 防止 CLASS_ISPREVERIFIED,补丁制作
原理请看这篇文字,这篇文章介绍的“实现 javassist 动态代码注入”使用 Groovy 开发的 demo 中代码注入,要一个个填写注入,本人写了一个 transformClasses.jar,实现批量代码注入,用法如下:
java -jar transformClasses.jar classPath libPath ignore
classPath:项目编译 class 所在目录
libPath:项目运行的依赖 class 路径,包括 AntilazyLoad.class 所在目录(注入代码必须用到)
ignore:忽略注入代码的类,如: *.APP packageName.* packageName.className
以上参数均可用;表示多个
//在 build.gradle 插入以下代码
//gradle1.4 以下使用这段
task('processWithJavassist') << {
println '-----------開始往 class 插入代碼-----------------'
String classPath = project(':app').buildDir.absolutePath + '/intermediates/classes/debug'//项目编译 class 所在目录
String libPath = "$rootDir/transformClasses;$rootDir/transformClasses/androidClass" //AntilazyLoad.class 及 android.jar 解压后的 class 所在目录
String ignore = "*.App;*.BuildConfig;online.magicbox.bugfix.*;online.magicbox.app.R.*;cn.jpush.*"
println classPath
println libPath
println ignore
javaexec {
classpath "$rootDir/transformClasses/transformClasses.jar"
main = 'cn.georgeyang.TransformClasses'
args classPath,libPath,ignore
}
}
//gradle1.5 及以上使用这段:
gradle.taskGraph.beforeTask { Task task ->
println "beforeTask:" + task.name + "," + task.group + "," + task.getProject().name
if (task.name.equals("preBuild") && task.getProject().name.equals("app")) {
String rootPath = project.rootDir.absolutePath
String jarPath = rootPath + "/transformClasses/transformClasses.jar"
String classPath = project.buildDir.absolutePath + '/intermediates/classes/debug'//项目编译 class 所在目录
String libPath = rootPath + "/transformClasses;" + rootPath+ "/transformClasses/androidClass" //AntilazyLoad.class 及 android.jar 解压后的 class 所在目录
String ignore = "*.App;*.BuildConfig;online.magicbox.bugfix.*;online.magicbox.app.R.*;cn.jpush.*"
project.javaexec {
classpath jarPath
main = 'cn.georgeyang.TransformClasses'
args classPath,libPath,ignore
}
}
}
//gradle1.5 及以上,自定义 gradlePlugIn 写法:
apply plugin: CodeInsert
class CodeInsert implements Plugin<Project> {
@Override
void apply(Project project) {
project.afterEvaluate {
project.android.applicationVariants.each { variant ->
def dexTaskName = "transformClassesWithDexFor${variant.name.capitalize()}"
def dexTask = project.tasks.findByName(dexTaskName)
if (dexTask) {
String rootPath = project.rootDir.absolutePath
String jarPath = rootPath + "/transformClasses/transformClasses.jar"
String classPath = project.buildDir.absolutePath + '/intermediates/classes/debug'//项目编译 class 所在目录
String libPath = rootPath + "/transformClasses;" + rootPath+ "/transformClasses/androidClass" //AntilazyLoad.class 及 android.jar 解压后的 class 所在目录
String ignore = "*.App;*.BuildConfig;online.magicbox.bugfix.*;online.magicbox.app.R.*;cn.jpush.*"
project.javaexec {
classpath jarPath
main = 'cn.georgeyang.TransformClasses'
args classPath,libPath,ignore
}
}
}
}
}
}
进过 transformClasses.jar 处理后,如何知道哪些类可以热修复?
查看 gradle console,会有如下结果
成功插入 AntilazyLoad 代码的会显示:
+ transform success: ***.class
失败会显示:
# transform fail: fail reasion - ***.class
R 文件不能热修复,所以被忽略:
# ingroe resoure: ***.class
指定忽略的会显示:
~ transform ignore by match: ignoreType - ***.class
只有 transform success 的类才能热更新
插件
加载插件代码原理: 重写 classloder
加载插件 View 实现:参考
插件开发依赖 devlibrary 项目
- 定义一个 Slice(类似 activity 的类,用于 pluginActivity 加载)
public class MainSlice extends Slice {
...
}
加载这个 Slice:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PluginActivity.init("cn.georgeyang.flashlight","magicbox","2");//包名,定义的 scheme,版本号
Intent intent = PluginActivity.buildIntent(this,MainSlice.class);
startActivity(intent);
finish();
}
}
- 插件中要使用 fragment
public class FileFragment extends PluginFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = getPluginLayoutInflater().inflate(R.layout.fragment_file,null);
}
//getPluginContext().getResources().getStringArray(R.array.item}
- 调用其他插件的图片选择器
Intent picker = PluginActivity.buildIntent(getActivity(),"online.magicbox.desktop","ImageSelectorSlice","1");
picker.putExtra("select_count_mode",0);
getActivity().startActivityForResult(picker,100);
- Slice 中的权限申请(fragment 也一样):
requestPermission(123,Manifest.permission.CAMERA);
@Override
public void onPermissionGiven(int code,String permission) {
super.onPermissionGiven(code,permission);
if (code == 123) {
Intent scan = PluginActivity.buildIntent(getActivity(), CaptureSlice2.class);
getActivity().startActivityForResult(scan, 50);
}
}
本项目中插件的缺点:
- 目前没有写 service,receiver 的支持
- Slice 继承的是 Context,而不是 activity,很多方法要谨慎使用
- 大量使用反射调用 app 宿主程序,效率减低
参考链接
CLASS_ISPREVERIFIED 的问题:
https://github.com/dodola/HotFix
https://github.com/bunnyblue/DroidFix
Javassist 使用: https://www.ibm.com/developerworks/cn/java/j-dyn0916/
gradle commend-line:
http://blog.csdn.net/innost/article/details/48228651
使用方法: 将 app-build-outputsd 的 app-debug.apk 提取 classes.dex 文件,将这个文件压缩,生成一个只有代码没有资源的 zip 包,这个包便是补丁包,
同一个项目下生成的补丁,兼容不同的签名,debug 生成的补丁也可以给 release 版本打补丁。
自定义 gradle 插件,让 AndHotFix 支持 gradle1.5+
gradle 知識:
全面文檔 http://tools.android.com/tech-docs/new-build-system/user-guide
資源改名 http://hugozhu.myalert.info/2014/08/03/50-use-gradle-to-customize-apk-build.html
Copyright and Licensing
Copyright george.yang © 2014-2015. All rights reserved.
This library is distributed under an MIT License.