Android组件生命周期管理

每个Android应用启动时,都会先创建一个Application。通常在Application里我们会做一些应用初始化的操作,常见的有第三方SDK初始化。在应用组件化之后,组件与壳工程是隔离开来的,但是组件有时候也需要获取应用的Application,也需要在应用启动时进行初始化。这就涉及到组件的生命周期管理问题。

最简单的实现方式如下:

  1. 我们定义一个Application的代理接口IAppLike出来,它模拟了Application的几个主要方法。
public interface IAppLike {      
    void onCreate();     
    void onTerminate();     
    void onLowMemory();    
    void onTrimMemory(int level);  
}
  1. 在组件内部写一个实现IAppLike的类,我们把这个类当做是组件的Application容器。这里的onCreate()等同于Application的onCreate()方法,组件可以在这里获取Application实例、执行启动初始化等操作,也可以在这里设置保存一些全局性的数据等。
public class NewsDelegate implements IApplicationDelegate {
    @Override
    public void onCreate() {
        Log.i("hx", "NewsDelegate-onCreate");
    }

    @Override
    public void onTerminate() {
        Log.i("hx", "NewsDelegate-onTerminate");
    }

    @Override
    public void onLowMemory() {
    }

    @Override
    public void onTrimMemory(int level) {
    }
}
  1. 在壳工程的Application.onCreate()方法里执行:
@Override 
public void onCreate() {     
   super.onCreate();     
   MainDelegate mainDelegate = new MainDelegate();     
   NewsDelegate newsDelegate = new NewsDelegate();     
   MusicDelegate musicDelegate = new MusicDelegate();     
   MimeDelegate mimeDelegate = new MimeDelegate(); 
   mainDelegate.onCreate();     
   newsDelegate.onCreate();     
   musicDelegate.onCreate();     
   mimeDelegate.onCreate();     
}

看起来貌似很简单,根本没什么技术含量,但是实际运用时,你会发现很多问题:
(1)组件初始化先后顺序问题:
前面介绍过,上层业务组件是依赖下层业务组件的,如果下层组件在应用启动时也需要初始化,那么我们在加载组件时,必然要先加载下层组件,否则加载上层组件时可能会出现问题。但是组件这么多,我们怎么确定要先加载谁后加载谁呢,当然你可以手动维护,代码里写死,但是当业务越来越多、时间越来越久,肯定不灵活,你新加一个业务组件进来,你都需要确定组件初始化先后顺序。所以,我们必须有个机制来确定组件初始化先后顺序。
类似线程优先级一样, 为每个组件定义了一个优先级,通过重写getPriority()方法可以设置组件的优先级。优先级范围从[1-10],默认优先级都为5,下层组件或需要先初始化的组件,优先级设置高一点。这样我们在加载组件的时候,先对所有组件的优先级进行排序,优先级高的排前面,然后再按顺序进行加载组件,就可解决这个问题了。

(2)自动加载问题
这里需要在壳工程代码里,手动构建各个组件的Delegate类。如果很多个组件都有实现该类,那在集成时得一个一个找出这些类,并且新增加一个组件,你都有可能要去修改壳工程代码,这样显然是不灵活且不利于代码维护的。如果能自动读取并加载这些Delegate类,那显然是极好的,这里有2种方式来实现:

  • 全局扫描代理类

应用启动时通过包名全局扫描源代码文件并找到代理类,通过反射去加载并初始化组件。这种方式只需要在组件中实现代理类即可,实现起来比较简单,但是全局扫描有性能的损耗。

  • 自定义gradle插件/字节码插入技术

通过自定义gradle插件和字节码插入技术来实现AOP,在编译期间在BaseApplication生命周期中动态注入字节码,实现调用代理类的对应方法。

全局扫描代理类

在BaseApplication初始化时扫描出所有组件的代理类,再通过反射加载并初始化。这里也有个问题,所有组件的代理类散落在各个不同的组件中,包名各不相同,当扫描到class文件后判断条件就必须是【class文件全类命.equal(组件1代理类全类命) || class文件全类命.equal(组件2代理类全类命) || …】,是不是很麻烦,这里我们可以采取apt技术,通过编译时注解动态生成这些代理类的辅助文件,在辅助文件中调用代理类的方法,而这些辅助文件的包名一样,这样我们只需要扫描辅助文件就可以了,判断条件就变成【class文件包名.startWith(辅助文件包名)】,是不是简单多了。

初步思路:

  1. 定义一个注解来标识实现了IAppLike的类。
  2. 通过APT技术,在组件编译时扫描和处理前面定义的注解,生成一个AppLike的代理类,姑且称之为AppLikeProxy,所有的代理类都在同一个包名下,这个包名下必须只包含代理类,且类名由固定的规则来生成,保证不同组件生成的代理类的类名不会重复。
  3. 需要有一个组件生命周期管理类,初始化时能扫描到固定包名下有多少个类文件,以及类文件的名称,这个固定包名就是前面我们生成的AppLikeProxy的包名,代理类都放在同一个包名下,是为了通过包名找到我们所有的目标类。
  4. 组件集成后在应用的Application.onCreate()方法里,调用组件生命周期管理类的初始化方法。
  5. 组件生命周期管理类的内部,扫描到所有的AppLikeProxy类名之后,通过反射进行类实例化。

初步技术难点:
需要了解APT技术,怎么在编译时动态生成java代码;关于编译时注解可以参考详解Android注解 Annotation这篇文章。
应用在运行时,怎么能扫描到某个包名下有多少个class,以及他们的名称呢;

注解定义

在Android Studio中,新建一个Java Library module,命名为lifecycle-annotation,在该module中创建一个注解类,同时创建一个后面要生成代理类的相关配置文件。
注解类:

@Retention(RetentionPolicy.CLASS) 
@Target(ElementType.TYPE) 
public @interface AppLifeCycle { }

配置文件:

public class LifeCycleConfig {      
/*** 生成代理类的包名      */     
public static final String PROXY_CLASS_PACKAGE_NAME = "com.example.lifecycle.apt.proxy";      
/*** 生成代理类统一的后缀      */     
public static final String PROXY_CLASS_SUFFIX = "$$Proxy";      
/*** 生成代理类统一的前缀      */     
public static final String PROXY_CLASS_PREFIX = "Watson$$";  }

使用APT来生成IAppLike的代理类

新建一个Java Library module,命名为lifecycle-apt,在该module里实现我们自己的注解处理器。

在build.gradle里修改配置为:

apply plugin: 'java-library'  

dependencies {     
implementation fileTree(dir: 'libs', include: ['*.jar'])      
implementation 'com.google.auto.service:auto-service:1.0-rc2'     
implementation project(':lifecycle-annotation')  
}  

sourceCompatibility = "1.8" 
targetCompatibility = "1.8"  

tasks.withType(JavaCompile) {     
   options.encoding = "UTF-8" 
}

接下来就是实现我们自己的注解处理器了:

@AutoService(Processor.class)
public class AppLikeProcessor extends AbstractProcessor {
    private Elements mElementUtils;
    private Map<String, AppLikeProxyClassCreator> mMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mElementUtils = processingEnvironment.getElementUtils();
    }

    /*** 支持解析的注解      */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(AppLifeCycle.class.getCanonicalName());
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AppLifeCycle.class);
        mMap.clear();
        for (Element element : elements) {
            if (!element.getKind().isClass()) {
                throw new RuntimeException("Annotation AppLifeCycle can only be used in class.");
            }
            TypeElement typeElement = (TypeElement) element;
            //这里检查一下,使用了该注解的类,同时必须要实现com.example.lifecycle.api.IAppLike接口,否则会报错,因为我们要实现一个代理类             
            List<? extends TypeMirror> mirrorList = typeElement.getInterfaces();
            if (mirrorList.isEmpty()) {
                throw new RuntimeException(typeElement.getQualifiedName() + " must implements interface com.example.lifecycle.api.IAppLike");
            }
            boolean checkInterfaceFlag = false;
            for (TypeMirror mirror : mirrorList) {
                if ("com.example.lifecycle.api.IAppLike".equals(mirror.toString())) {
                    checkInterfaceFlag = true;
                }
            }
            if (!checkInterfaceFlag) {
                throw new RuntimeException(typeElement.getQualifiedName() + " must implements interface com.example.lifecycle.api.IAppLike");
            }
            String fullClassName = typeElement.getQualifiedName().toString();
            if (!mMap.containsKey(fullClassName)) {
                System.out.println("process class name : " + fullClassName);
                AppLikeProxyClassCreator creator = new AppLikeProxyClassCreator(mElementUtils, typeElement);
                mMap.put(fullClassName, creator);
            }
        }
        System.out.println("start to generate proxy class code");
        for (Map.Entry<String, AppLikeProxyClassCreator> entry : mMap.entrySet()) {
            String className = entry.getKey();
            AppLikeProxyClassCreator creator = entry.getValue();
            System.out.println("generate proxy class for " + className);
            try {
                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(creator.getProxyClassFullName());
                Writer writer = jfo.openWriter();
                writer.write(creator.generateJavaCode());
                writer.flush();
                writer.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}
public class AppLikeProxyClassCreator {
    private Elements mElementUtils;
    private TypeElement mTypeElement;
    private String mProxyClassSimpleName;

    public AppLikeProxyClassCreator(Elements elements, TypeElement typeElement) {
        mElementUtils = elements;
        mTypeElement = typeElement;
        mProxyClassSimpleName = LifeCycleConfig.PROXY_CLASS_PREFIX + mTypeElement.getSimpleName().toString() + LifeCycleConfig.PROXY_CLASS_SUFFIX;
    }

    /**
     * 获取要生成的代理类的完整类名      *      * @return
     */
    public String getProxyClassFullName() {
        String name = LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME + "." + mProxyClassSimpleName;
        return name;
    }

    /**
     * 生成java代码
     */
    public String generateJavaCode() {
        StringBuilder sb = new StringBuilder();
        //设置包名         
        sb.append("package ").append(LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME).append(";\n\n");
        //设置import部分         
        sb.append("import android.content.Context;\n");
        sb.append("import com.example.lifecycle.api.IAppLike;\n");
        sb.append("import ").append(mTypeElement.getQualifiedName()).append(";\n\n");
        sb.append("public class ").append(mProxyClassSimpleName).append(" implements ").append("IAppLike ").append(" {\n\n");
        //设置变量         
        sb.append("  private ").append(mTypeElement.getSimpleName().toString()).append(" mAppLike;\n\n");
        //构造函数         
        sb.append("  public ").append(mProxyClassSimpleName).append("() {\n");
        sb.append("  mAppLike = new ").append(mTypeElement.getSimpleName().toString()).append("();\n");
        sb.append("  }\n\n");
        //getPriority()方法         
        sb.append("  public int getPriority() {\n");
        sb.append("    return mAppLike.getPriority();\n");
        sb.append("  }\n\n");
        //onCreate()方法         
        sb.append("  public void onCreate() {\n");
        sb.append("    mAppLike.onCreate();\n");
        sb.append("  }\n\n");
        //onTerminate方法         
        sb.append("  public void onTerminate() {\n");
        sb.append("    mAppLike.onTerminate();\n");
        sb.append("  }\n\n");
        //onTerminate方法         
        sb.append("  public void onLowMemory() {\n");
        sb.append("    mAppLike.onLowMemory();\n");
        sb.append("  }\n\n");
        //onTerminate方法        
        sb.append("  public void onTrimMemory(int level) {\n");
        sb.append("    mAppLike.onTrimMemory(level);\n");
        sb.append("  }\n\n");
        sb.append("\n}");
        return sb.toString();
    }
}

关于APT的调试可以参考https://blog.csdn.net/zhangteng22/article/details/54946270这篇文章。

在前面定义的几个组件代理类上添加注解@AppLifeCycle

//实现了IAppLike接口,并且采用了AppLifeCycle注解,二者缺一不可,否则APT处理时会报错 
@AppLifeCycle
public class MainDelegate implements IAppLike {
    @Override
    public int getPriority() {
        return 10;
    }

    @Override
    public void onCreate() {
        Log.i("hx", "MainDelegate-onCreate");
    }

    @Override
    public void onTerminate() {
        Log.i("hx", "MainDelegate-onTerminate");
    }

    @Override
    public void onLowMemory() {
    }

    @Override
    public void onTrimMemory(int level) {
    }
}

同时修改组件的build.gradle,添加依赖:

//---------其他依赖------------     
api project(':lifecycle-annotation')     
api project(':lifecycle-api')     
//需要注意这里是使用 annotationProcessor,即我们刚定义的注解处理器     
annotationProcessor project(':lifecycle-apt')

到这里注解处理器就可以工作啦,将整个工程编译一下,可以看到在build目录下已经生成了我们定义的注解类,具体路径如下所示:
在这里插入图片描述
打开看一下呢:

public class Watson$$MainDelegate$$Proxy implements IAppLike {
    private MainDelegate mAppLike;

    public Watson$$MainDelegate$$Proxy() {
        mAppLike = new MainDelegate();
    }

    public int getPriority() {
        return mAppLike.getPriority();
    }

    public void onCreate() {
        mAppLike.onCreate();
    }

    public void onTerminate() {
        mAppLike.onTerminate();
    }

    public void onLowMemory() {
        mAppLike.onLowMemory();
    }

    public void onTrimMemory(int level) {
        mAppLike.onTrimMemory(level);
    }
}

重新定义IAppLike接口

新建一个Android Library module,命名为lifecycle-api,在这个module里定义IAppLike接口,以及一个生命周期管理类。现在工程目录差不多是这样的:
捕获.PNG

public interface IAppLike {
    int MAX_PRIORITY = 10;
    int MIN_PRIORITY = 1;
    int NORM_PRIORITY = 5;

    default int getPriority() {
        return NORM_PRIORITY;
    }

    void onCreate();

    void onTerminate();

    void onLowMemory();

    void onTrimMemory(int level);
}

生命周期管理类,初始化的时候扫描所有的代理辅助类,并在相应方法中调用组件生命周期方法。

public class AppLifeCycleManager {
    /**
     * 初始化
     */
    public static void init(Context context) {
        scanClassFile(context);
        Log.d("hx", "代理子模块数目:" + APP_LIKE_LIST.size());
        Collections.sort(APP_LIKE_LIST, new AppDelegateComparator()); //优先级排序    
    }

    public static void onCreate() {
        for (IAppLike delegate : APP_LIKE_LIST) {
            delegate.onCreate();
        }
    }

    public static void onTerminate() {
        for (IAppLike delegate : APP_LIKE_LIST) {
            delegate.onTerminate();
        }
    }

    public static void onLowMemory() {
        for (IAppLike delegate : APP_LIKE_LIST) {
            delegate.onLowMemory();
        }
    }

    public static void onTrimMemory(int level) {
        for (IAppLike delegate : APP_LIKE_LIST) {
            delegate.onTrimMemory(level);
        }
    }

    /**
     * 扫描出固定包名下,实现了IAppLike接口的代理类      *      * @param context
     */
    private static void scanClassFile(Context context) {
        try {
            Set<String> set = ClassUtils.getFileNameByPackageName(context, LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME);
            if (set != null) {
                for (String className : set) {
                    Log.d("hx", className);
                    try {
                        Object obj = Class.forName(className).newInstance();
                        if (obj instanceof IAppLike) {
                            APP_LIKE_LIST.add((IAppLike) obj);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 优先级比较器,优先级大的排在前面
     */
    static class AppDelegateComparator implements Comparator<IAppLike> {
        @Override
        public int compare(IAppLike o1, IAppLike o2) {
            int p1 = o1.getPriority();
            int p2 = o2.getPriority();
            return p2 - p1;
        }
    }
}

BaseApplication修改

public class BaseApplication extends Application {
    private static BaseApplication sInstance;

    public static BaseApplication getIns() {
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        if (BuildConfig.DEBUG) {
            ARouter.openLog();  // 打印日志             
            ARouter.openDebug();    // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)         
        }
        ARouter.init(this);// 尽可能早,推荐在Application中初始化         
        // 代理方法         
        AppLifeCycleManager.init(this);
        AppLifeCycleManager.onCreate();
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        ARouter.getInstance().destroy();
        AppLifeCycleManager.onTerminate();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        AppLifeCycleManager.onLowMemory();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        AppLifeCycleManager.onTrimMemory(level);
    }
}

在BaseApplication的生命周期中调用Manager中相应的方法即可。

跑一下,看日志输入,可以看到扫描到了四个代理辅助类,在BaseApplication的onCreate方法中分别调用了四个代理类的onCreate方法,且按照优先级顺序调用。
捕获.PNG

更进一步的思考:
前面的思路里,应用在启动时,需要扫描dex文件里的所有class,然后通过class文件的包名来判断是不是我们的目标类。通常一个安装包里,加上第三方库,class文件可能数以千计、数以万计,但是我们的要用到的可能只有几个,这让人有点杀鸡用牛刀的感觉。能不能在运行时,不扫描所有class文件,就已经知道了所有的AppLikeProxy类名呢?首先想到的就是采用gradle插件技术,在应用打包编译时,动态插入字节码来实现。这里又会碰到几个技术难点:

  1. 怎么制作gradle插件。可以参考Gradle自定义插件这篇文章。
  2. 怎么在打包时动态插入字节码。可以参考Android字节码插桩这篇文章。

自定义gradle插件/字节码插入技术

采用groovy创建插件

新建一个Java Library module,命名为lifecycle-plugin,删除 src->main 下面的java目录,新建一个groovy目录,在groovy目录下创建类似java的package,在 src->main 下面创建一个 resources 目录,在resources目录下依次创建 META-INF/gradle-plugins 目录,最后在该目录下创建一个名为 com.example.plugin.lifecycle.properties的文本文件,文件名是你要定义的插件名,按需自定义即可。最后的工程结构如图所示:
捕获.PNG
修改module的build.gradle文件,引入groovy插件等:

apply plugin:'java-library'
apply plugin:'groovy'
apply plugin:'maven'

dependencies{
    implementation fileTree(dir:'libs',include:['*.jar'])
    compile gradleApi()
    compile localGroovy()
    compile'com.android.tools.build:transform-api:1.5.0'
    compile'com.android.tools.build:gradle:3.0.1'
}

sourceCompatibility="1.8"
targetCompatibility="1.8"
        
tasks.withType(JavaCompile){
    options.encoding="UTF-8"
}  

//通过maven将插件发布到本地的脚本配置,根据自己的要求来修改 
uploadArchives {     
    repositories.mavenDeployer {         
        pom.version = '1.0.0'         
        pom.artifactId = 'lifecycleplugin'         
        pom.groupId = 'com.example.component'         
        repository(url: "file:///D:/repository/")     
    } 
}

这里有几点需要说明的是:

  1. 通常都是采用groovy语言来创建gradle
    plugin的,groovy是兼容java的,你完全可以采用java来编写插件。关于groovy语言,了解一些基础语法就足够支撑我们去编写插件了。

  2. src/main/resources/META-INF/gradle-plugins目录下定义插件声明,*.properties文件的文件名就是插件名称。

使用插件

我们通过maven将该插件发布到本地的maven仓库里,发布成功后,我们在app module里引入该插件,修改app module目录下的build.gradle文件,增加如下配置:

//自定义插件 
apply plugin: 'com.example.plugin.lifecycle' 
buildscript {     
   repositories {        
      google()         
      jcenter()         
      //自定义插件maven地址         
      maven {             
         url uri('D:/repository')  //add         
      }     
    }     

    dependencies {         
      //加载自定义插件         
      classpath 'com.example.component:lifecycleplugin:1.0.0'     
      } 
}

实现Plugin接口

要编写一个插件是很简单的,只需实现Plugin接口即可,在里面注册LifeCycleTransform:

class LifeCyclePlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        println "------LifeCycle plugin entrance-------"
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(new LifeCycleTransform(project))
    }
}

接着在com.example.plugin.lifecycle.properties文件里增加配置:

implementation-class=com.example.lifecycle.plugin.LifeCyclePlugin

其中implementation-class的值为Plugin接口的实现类的全限定类名。

那么怎么通过插件在打包前去扫描所有的class文件呢,幸运的是官方给我们提供了 Gradle Transform 技术,简单来说就是能够让开发者在项目构建阶段即由class到dex转换期间修改class文件,Transform阶段会扫描所有的class文件和资源文件,下面通过代码部分说下我的思路:

class LifeCycleTransform extends Transform {
    Project project

    LifeCycleTransform(Project project) {
        this.project = project
    }

    //该Transform的名称,自定义即可,只是一个标识     
    @Override
    String getName() {
        return "LifeCycleTransform"
    }

    //该Transform支持扫描的文件类型,分为class文件和资源文件,我们这里只处理class文件的扫描     
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    //Transform的扫描范围,我这里扫描整个工程,包括当前module以及其他jar包、aar文件等所有的class     
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    //是否增量扫描     
    @Override
    boolean isIncremental() {
        return true
    }

    @Override
    void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
        println "\nstart to transform-------------->>>>>>>"
        def appLikeProxyClassList = []
        //inputs就是所有扫描到的class文件或者是jar包,一共2种类型         
        inputs.each {
            TransformInput input ->
            //1.遍历所有的class文件目录             
            input.directoryInputs.each {
                DirectoryInput directoryInput ->
                //递归扫描该目录下所有的class文件                 
                if (directoryInput.file.isDirectory()) {
                    directoryInput.file.eachFileRecurse {
                        File file ->
                        //形如 Watson****$$Proxy.class 的类,是我们要找的目标class                         
                        if (ScanUtil.isTargetProxyClass(file)) {
                            //如果是我们自己生产的代理类,保存该类的类名                             
                            appLikeProxyClassList.add(file.name)
                        }
                    }
                }
                //Transform扫描的class文件是输入文件(input),有输入必然会有输出(output),处理完成后需要将输入文件拷贝到一个输出目录下去,                 
                // 后面打包将class文件转换成dex文件时,直接采用的就是输出目录下的class文件了。                 
                // 必须这样获取输出路径的目录名称                 
                def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                FileUtils.copyDirectory(directoryInput.file, dest)
            }
            //2.遍历查找所有的jar包             
            input.jarInputs.each {
                JarInput jarInput ->println
                "\njarInput = ${jarInput}"
                //与处理class文件一样,处理jar包也是一样,最后要将inputs转换为outputs                 
                def jarName = jarInput.name
                def md5 = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                if (jarName.endsWith(".jar")) {
                    jarName = jarName.substring(0, jarName.length() - 4)
                }
                //获取输出路径下的jar包名称,必须这样获取,得到的输出路径名不能重复,否则会被覆盖
                def dest = outputProvider.getContentLocation(jarName + md5, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                if (jarInput.file.getAbsolutePath().endsWith(".jar")) {
                    //处理jar包里的代码                    
                    File src = jarInput.file
                    //先简单过滤掉 support-v4 之类的jar包,只处理有我们业务逻辑的jar包                     
                    if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) {
                        //扫描jar包的核心代码在这里,主要做2件事情:                         
                        // 1.扫描该jar包里有没有实现IAppLike接口的代理类;                         
                        // 2.扫描AppLifeCycleManager这个类在哪个jar包里,并记录下来,后面需要在该类里动态注入字节码;                         
                        List<String> list = ScanUtil.scanJar(src, dest) if (list != null) {
                            appLikeProxyClassList.addAll(list)
                        }
                    }
                }
                //将输入文件拷贝到输出目录下                 
                FileUtils.copyFile(jarInput.file, dest)
            }
        }
        println "" appLikeProxyClassList.forEach({fileName -> println"file name = " + fileName})
        println "\n包含AppLifeCycleManager类的jar文件" println ScanUtil.
        FILE_CONTAINS_INIT_CLASS.getAbsolutePath() println
        "开始自动注册"
        //1.通过前面的步骤,我们已经扫描到所有实现了 IAppLike接口的代理类;         
        // 2.后面需要在 AppLifeCycleManager 这个类的初始化方法里,动态注入字节码;         
        // 3.将所有 IAppLike 接口的代理类,通过类名进行反射调用实例化         
        // 这样最终生成的apk包里,AppLifeCycleManager调用init()方法时,已经可以加载所有组件的生命周期类了        
        new AppLikeCodeInjector(appLikeProxyClassList).execute()
        println "transform finish----------------<<<<<<<\n"
    }
}

我们来看看ScanUtil类里的代码逻辑:

class ScanUtil {
    static final PROXY_CLASS_PREFIX ="Watson\$\$"static final PROXY_CLASS_SUFFIX ="\$\$Proxy.class"static final PROXY_CLASS_PACKAGE_NAME ="com/example/lifecycle/apt/proxy"static final REGISTER_CLASS_FILE_NAME ="com/example/lifecycle/api/AppLifeCycleManager.class"static final REGISTER_CLASS_METHOD_NAME ="loadApplicationDelegate"
    //包含生命周期管理初始化类的文件,及包含 AppLifeCycleManager 类的class文件或者jar文件
    static File FILE_CONTAINS_INIT_CLASS

    /**
     * 判断该class是否是我们的目标类
     */
    static boolean isTargetProxyClass(File file) {
        if (file.name.endsWith(PROXY_CLASS_SUFFIX) && file.name.startsWith(PROXY_CLASS_PREFIX)) {
            return true
        } return false
    }

    /**
     * 扫描jar包里的所有class文件:
     * * 1.通过包名识别所有需要注入的类名
     * * 2.找到注入管理类所在的jar包,后面我们会在该jar包里进行代码注入
     */
    static List<String> scanJar(File jarFile, File destFile) {
        def file = new JarFile(jarFile) Enumeration<JarEntry> enumeration = file.entries()
        List<String> list = null while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = enumeration.nextElement() String entryName = jarEntry.getName()
            if (entryName == REGISTER_CLASS_FILE_NAME) {
                //标记这个jar包包含 AppLifeCycleManager.class                 
                // 扫描结束后,我们会生成注册代码到这个文件里                 
                FILE_CONTAINS_INIT_CLASS = destFile
            } else {
                if (entryName.startsWith(PROXY_CLASS_PACKAGE_NAME)) {
                    if (list == null) {
                        list = new ArrayList<>()
                    } list.addAll(entryName.substring(entryName.lastIndexOf("/") + 1))
                }
            }
        }
        return list
    }

    static boolean shouldProcessPreDexJar(String path) {
        return !path.contains("com.android.support") && !path.contains("/android/m2repository")
    }
}

前面的代码里,先注释掉LifeCycleTransform类里的AppLikeCodeInjector相关代码,这块我们后面再讲。采用最新的插件重新build一下工程,看看Gradle Console里的输出信息。

捕获.PNG

可以看到,在Transform过程中,我们找到了NewsDelegate、MusicDelegate、MimeDelegate、MainDelegate这4个类的代理类,以及AppLifeCycleManager这个class文件所在的jar包。

捕获.PNG

在app->build->intermediates->transforms中,可以看到所有的Transform,包括我们刚才自定义的LifeCycleTransform。从上图中可以看到,这里的0.jar、1.jar、2.jar等等,都是通过outputProvider.getContentLocation()方法来生成的,这个Transform目录下的class文件、jar包等,会当做下一个Transform的inputs传递过去。

通过ASM动态修改字节码

到现在,我们只剩下最后一步了,那就是如何注入代码了。ASM 是一个 Java 字节码操控框架,它能被用来动态生成类或者增强既有类的功能。我这里对ASM不做详细介绍了,主要是介绍使用ASM动态注入代码的思路。
首先,我们修改一下AppLifeCycleManager类,增加动态注入字节码的入口方法:

public class AppLifeCycleManager {
    private static List<IAppLike> APP_LIKE_LIST = new ArrayList<>();
    private static boolean REGISTER_BY_PLUGIN = false;

    /**
     * 通过插件加载 IApplicationDelegate 类
     */
    private static void loadApplicationDelegate() {
    }

    private static void registerApplicationDelegate(String className) {
        if (TextUtils.isEmpty(className)) return;
        try {
            Object obj = Class.forName(className).getConstructor().newInstance();
            if (obj instanceof IAppLike) {
                registerApplicationDelegate((IAppLike) obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 注册IApplicationDelegate
     */
    private static void registerApplicationDelegate(IAppLike appLike) {
        //标志我们已经通过插件注入代码了         
        REGISTER_BY_PLUGIN = true;
        APP_LIKE_LIST.add(appLike);
    }

    /**
     * 初始化
     */
    public static void init(Context context) {
        loadApplicationDelegate();
        if (!REGISTER_BY_PLUGIN) {
            Log.d("hx", "需要扫描所有类...");
            scanClassFile(context);
        } else {
            Log.d("hx", "插件里已自动注册...");
        }
        Log.d("hx", "代理子模块数目:" + APP_LIKE_LIST.size());
        Collections.sort(APP_LIKE_LIST, new AppDelegateComparator()); //优先级排序     
    }    
    ...
}

相比之前,这里增加了一个loadApplicationDelegate()方法,在init()方法调用时会先执行。通过前面Transform步骤之后,我们现在的目标是把代码动态插入到loadApplicationDelegate()方法里,下面这段代码是我们期望插入后的结果:

private static void loadAppLike() {   
   registerAppLike("com.example.lifecycle.apt.proxy.Watson$$MusicDelegate$$Proxy");   
   registerAppLike("com.example.lifecycle.apt.proxy.Watson$$MainDelegate$$Proxy");   
   registerAppLike("com.example.lifecycle.apt.proxy.Watson$$MimeDelegate$$Proxy");   
   registerAppLike("com.example.lifecycle.apt.proxy.Watson$$NewsDelegate$$Proxy");
}

这样在初始化时,就已经知道要加载哪些生命周期类,来看看具体实现方法,关于ASM不了解的地方,需要先搞清楚其使用方法再来阅读:

class AppLikeCodeInjector {
    List<String> proxyAppLikeClassList

    AppLikeCodeInjector(List<String> list) {
        proxyAppLikeClassList = list
    }

    void execute() {
        println("开始执行ASM方法======>>>>>>>>")
        File srcFile = ScanUtil.FILE_CONTAINS_INIT_CLASS
        //创建一个临时jar文件,要修改注入的字节码会先写入该文件里         
        def optJar = new File(srcFile.getParent(), srcFile.name + ".opt")
        if (optJar.exists()) optJar.delete() def file = new JarFile(srcFile)
        Enumeration<JarEntry> enumeration = file.entries()
        JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))
        while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = enumeration.nextElement() String entryName = jarEntry.getName()
            ZipEntry zipEntry = new ZipEntry(entryName)
            InputStream inputStream = file.getInputStream(jarEntry)
            jarOutputStream.putNextEntry(zipEntry)
            //找到需要插入代码的class,通过ASM动态注入字节码             
            if (ScanUtil.REGISTER_CLASS_FILE_NAME == entryName) {
                println "insert register code to class >> " + entryName
                ClassReader classReader = new ClassReader(inputStream)
                // 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小                 
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS)
                ClassVisitor classVisitor = new AppLikeClassVisitor(classWriter)
                //开始扫描class文件                 
                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
                byte[] bytes = classWriter.toByteArray()
                //将注入过字节码的class,写入临时jar文件里                 
                jarOutputStream.write(bytes)
            } else {
                //不需要修改的class,原样写入临时jar文件里                 
                jarOutputStream.write(IOUtils.toByteArray(inputStream))
            } inputStream.close() jarOutputStream.closeEntry()
        } jarOutputStream.close()
        file.close()
        //删除原来的jar文件         
        if (srcFile.exists()) {
            srcFile.delete()
        }
        //重新命名临时jar文件,新的jar包里已经包含了我们注入的字节码了         
        optJar.renameTo(srcFile)
    }

    class AppLikeClassVisitor extends ClassVisitor {
        AppLikeClassVisitor(ClassVisitor classVisitor) {
            super(Opcodes.ASM5, classVisitor)
        }

        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exception) {
            println "visit method: " + name
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exception)
            //找到 AppLifeCycleManager里的loadApplicationDelegate()方法             
            if (ScanUtil.REGISTER_CLASS_METHOD_NAME == name) {
                mv = new LoadAppLikeMethodAdapter(mv, access, name, desc)
            } return mv
        }
    }

    class LoadAppLikeMethodAdapter extends AdviceAdapter {
        LoadAppLikeMethodAdapter(MethodVisitor mv, int access, String name, String desc) {
            super(Opcodes.ASM5, mv, access, name, desc)
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter() println "-------onMethodEnter------"
            proxyAppLikeClassList.forEach({proxyClassName -> println"开始注入代码:${proxyClassName}"def fullName = ScanUtil.PROXY_CLASS_PACKAGE_NAME.replace("/", ".") + "." + proxyClassName.substring(0, proxyClassName.length() - 6)println"full classname = ${fullName}"mv.visitLdcInsn(fullName)mv.visitMethodInsn(INVOKESTATIC, "com/example/lifecycle/api/AppLifeCycleManager", "registerApplicationDelegate", "(Ljava/lang/String;)V", false);             })}

        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode) println "-------onMethodEnter------"
        }
    }
}

clean工程(不然有时候看不到transform中输出的console log),使用插件重新编译代码:

捕获.PNG

可以看到在编译过程中已经字节码增强注入了新的代码,最后打开APP运行,验证结果。
捕获.PNG
DEMO下载地址

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值