#MagicBox
一款热修复和插件技术集一身app,下载地址
演示效果
- 目前里面有10款免安装的插件软件:
1.手电筒
2.wifi连接
3.wifi密码查看
4.火车票余票查询
5.计算器
6.俄罗斯方块
7.csdn阅读
8.拨号声识别
9.hash-e
10.二维码
- 依赖devlibrary开发插件,写出的app既可以独立运行,也可以作为插件运行
- 代码动态修复 原理
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 george.yang © 2014-2015. All rights reserved.
This library is distributed under an MIT License.