magicbox

Project Url: qq179157977/magicbox
Introduction: 一款热修复和插件集一身的开源 app
More: Author   ReportBugs   DemoAPK   
Tags:
hotfix-plugin-插件化-热修复-

一款热修复和插件技术集一身 app,下载地址

演示效果

weiget Effect

  • 目前里面有 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://stackoverflow.com/questions/29289200/in-gradle-exec-task-commandline-searching-environment-but-not-working-directory

http://blog.csdn.net/innost/article/details/48228651

使用方法: 将 app-build-outputsd 的 app-debug.apk 提取 classes.dex 文件,将这个文件压缩,生成一个只有代码没有资源的 zip 包,这个包便是补丁包,

同一个项目下生成的补丁,兼容不同的签名,debug 生成的补丁也可以给 release 版本打补丁。

自定义 gradle 插件,让 AndHotFix 支持 gradle1.5+

自定义 gradle 插件

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.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools