美文网首页
Android组件化

Android组件化

作者: R7_Perfect | 来源:发表于2024-05-14 18:26 被阅读0次

    组件指的是单一的功能组件,如 [视频组件]、[支付组件] 等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用。

    下载.png

    每个组件都是一个完整的整体,所以组件开发过程中要满足单独运行及调试的要求,这样还可以提升开发过程中项目的编译速度。

    1. 单独调试

    动态配置组件的工程类型

    (fd7d25fec93544238518ebc9491cca7e~tplv-k3u1fbpfcp-watermark.image.jpg) fd7d25fec93544238518ebc9491cca7e~tplv-k3u1fbpfcp-watermark.image.jpg

    动态配置组件的 ApplicationId 和 AndroidManifest 文件

    ApplicationId 和 AndroidManifest 文件都是可以在 build.gradle 文件中进行配置的,所以我们同样通过动态配置组件工程类型时定义的 isRunAlone 这个变量的值来动态修改ApplicationId 和 AndroidManifest。
    首先我们要新建一个 AndroidManifest.xml 文件,加上原有的 AndroidManifest 文件,在两个文件中就可以分别配置单独调试和集成调试时的不同的配置,如图:

    image.png
    // main/manifest/AndroidManifest.xml 单独调试
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.loong.share">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".ShareActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    
    // main/AndroidManifest.xml 集成调试
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.loong.share">
    
        <application android:theme="@style/AppTheme">
            <activity android:name=".ShareActivity"/>
        </application>
    
    </manifest>
    

    然后在 build.gradle 中通过判断 isRunAlone 的值,来配置不同的 ApplicationId 和 AndroidManifest.xml 文件的路径:

    // share 组件的 build.gradle
    
    android {
        defaultConfig {
            if (isRunAlone.toBoolean()) {
                // 单独调试时添加 applicationId ,集成调试时移除
                applicationId "com.loong.login"
            }
            ...
        }
        
        sourceSets {
            main {
                // 单独调试与集成调试时使用不同的 AndroidManifest.xml 文件
                if (isRunAlone.toBoolean()) {
                    manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    }
    

    2. 组件 Application 的动态配置

    第一步:

    在 Base 模块中定义抽象类 BaseApp 继承 Application,里面定义了两个方法,initModeApp 是初始化当前组件时需要调用的方法,initModuleData 是所有组件的都初始化后再调用的方法。

    // Base 模块中定义
    public abstract class BaseApp extends Application {
        /**
         * Application 初始化
         */
        public abstract void initModuleApp(Application application);
    
        /**
         * 所有 Application 初始化后的自定义操作
         */
        public abstract void initModuleData(Application application);
    }
    

    第二步:

    所有的组件的 Application 都继承 BaseApp,并在对应的方法中实现操作,我们这里还是以 Login 组件为例,其 LoginApp 实现了 BaseApp 接口,其 initModuleApp 方法中完成了在 ServiceFactory 中注册自己的 Service 对象。在单独调试时 onCreate() 方法中也会调用 initModuleApp() 方法完成在 ServiceFactory 中的注册操作。

    // Login 组件的 LoginApp
    public class LoginApp extends BaseApp {
    
        @Override
        public void onCreate() {
            super.onCreate();
            initModuleApp(this);
            initModuleData(this);
        }
    
        @Override
        public void initModuleApp(Application application) {
            
        }
    
        @Override
        public void initModuleData(Application application) {
    
        }
    }
    

    第三步:

    在 Base 模块中定义 AppConfig 类,其中的 moduleApps 是一个静态的 String 数组,我们将需要初始化的组件的 Application 的完整类名放入到这个数组中

    // Base 模块的 AppConfig
    public class AppConfig {
        private static final String LoginApp = "com.loong.login.LoginApp";
    
        public static String[] moduleApps = {
                LoginApp
        };
    }
    

    第四步:

    主 module 的 Application 也继承 BaseApp ,并实现两个初始化方法,在这两个初始化方法中遍历 AppcConfig 类中定义的 moduleApps 数组中的类名,通过反射,初始化各个组件的 Application。

    // 主 Module 的 Applicaiton
    public class MainApplication extends BaseApp {
        @Override
        public void onCreate() {
            super.onCreate();
            
            // 初始化组件 Application
            initModuleApp(this);
            
            // 其他操作
            
            // 所有 Application 初始化后的操作
            initModuleData(this);
            
        }
    
        @Override
        public void initModuleApp(Application application) {
            for (String moduleApp : AppConfig.moduleApps) {
                try {
                    Class clazz = Class.forName(moduleApp);
                    BaseApp baseApp = (BaseApp) clazz.newInstance();
                    baseApp.initModuleApp(this);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void initModuleData(Application application) {
            for (String moduleApp : AppConfig.moduleApps) {
                try {
                    Class clazz = Class.forName(moduleApp);
                    BaseApp baseApp = (BaseApp) clazz.newInstance();
                    baseApp.initModuleData(this);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    进阶

    也可以使用注解方式实现代码自动初始化,生命周期自动派发,结合plugin方式,在编译时生成相关代码:

    1. 定义接口

    public interface IAppLike {
        // 每个time type对应不同的初始化时机
        int TIME_TYPE_MAIN = 1;          // 主线程立刻初始化
        ...
    
         void onCreate(Context context, int timeType);
        ...
    }
    

    2. 辅助基类

    public class DefaultAppLike implements IAppLike {
         @Override
        public void onCreate(Context context, int timeType) {
            if (timeType == IAppLike.TIME_TYPE_MAIN) {
                onCreateByMain(context);
            }
            ...
        }
    
        public void onCreateByMain(Context context) {
        }
        ...
    }
    

    3. 实际应用:

    class FirstAppLike : DefaultAppLike() {
    
        override fun onCreate(context: Context?) {
            super.onCreate(context)
            application = context as Application?
           //do something initial
        }
    
        companion object {
            var application: Application? = null
                private set
        }
    }
    

    如何能自动在壳App的Application onCreate中调用WeexAppLike 中的 onCreate方法呢
    我们可以借助Annotation和Processor生成XXProxy代码:

    1. 定义Annotation

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

    2, Processor:

    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith((Class)AppLifeCycle.class);
        ...
        for (Element element : elements) {
         ...
          TypeElement typeElement = (TypeElement)element;
          ...
          String fullClassName = typeElement.getQualifiedName().toString();
          if (!this.mMap.containsKey(fullClassName)) {
            AppLikeProxyClassCreator creator = new AppLikeProxyClassCreator(this.mElementUtils, typeElement);
            this.mMap.put(fullClassName, creator);
          } 
        } 
        for (Map.Entry<String, AppLikeProxyClassCreator> entry : this.mMap.entrySet()) {
        ...
          String className = entry.getKey();
          AppLikeProxyClassCreator creator = entry.getValue();
          ...
          try {
            JavaFileObject jfo = this.processingEnv.getFiler().createSourceFile(creator.getProxyClassFullName(), new Element[0]);
            Writer writer = jfo.openWriter();
            writer.write(creator.generateJavaCode());
            writer.flush();
            writer.close();
          } catch (Exception e) {
            e.printStackTrace();
          } 
        } 
        return true;
      }
    

    其中AppLikeProxyClassCreator的generateJavaCode代码是生成XXProxy类,具体如何生成可以参照JavaPoet方式生成

    3. 集成

    implementation xxx:annotation:1.0.0'
    annotationProcessor xxx:processor:1.0.0'
    
    @AppLifeCycle
    class FirstAppLike : DefaultAppLike() {
    ...
    }
    

    在模块编译compileXXXJavaWithJavac的时候,会运行processor
    最终生成的代码:

    public class Robin$$FirstAppLike$$Proxy implements IAppLike {
    
        private FirstAppLike mAppLike;
    
        public Robin$$FirstAppLike$$Proxy() {
            mAppLike = new FirstAppLike();
        }
        ...
        public void onCreate(Context context, int timeType) {
            mAppLike.onCreate(context, timeType);
        }
        ...
    }
    

    4.植入代码

    gradle plugin通过AMS方式编译时植入代码

    定义一个Manager类,作用是自动将注解生成的类注册到主Application中

    public class AppLifeCycleManager {
    ...
        private static void loadAppLike() {
        }
    
        private static void registerAppLike(String className) {
            if (TextUtils.isEmpty(className))
                return;
    
            try {
                Object obj = Class.forName(className).getConstructor().newInstance();
                if (obj instanceof IAppLike) {
                    APP_LIKE_LIST.add((IAppLike) obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    ...
    }
    

    我们将用AMS处理这个类
    编写Transformer:

    class LifeCycleTransform extends Transform {
    
      @Override
        void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs,
                       TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
            ...
                    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)
            ...
        }
    }
    
    
    class AppLikeClassVisitor extends ClassVisitor {
         ...
            @Override
            MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exception) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exception)
                // 找到 AppLifeCycleManager里的loadAppLike()方法
                if ("loadAppLike" == name) {
                    mv = new LoadAppLikeMethodAdapter(mv, access, name, desc)
                }
                return mv
            }
        }
    
        class LoadAppLikeMethodAdapter extends AdviceAdapter {
        ...
    
            @Override
            protected void onMethodEnter() {
                super.onMethodEnter()
                proxyAppLikeClassList.forEach({ proxyClassName ->
                    def fullName = PROXY_CLASS_PACKAGE_NAME.replace("/", ".") + "." + proxyClassName.substring(0, proxyClassName.length() - 6)
                    mv.visitLdcInsn(fullName)
                    mv.visitMethodInsn(INVOKESTATIC, "xxx/lifecycle/api/AppLifeCycleManager", "registerAppLike", "(Ljava/lang/String;)V", false)
                })
            }
            ...
        }
    
    

    针对AppLifeCycleManager类,找到loadAppLike方法,则运行registerAppLike方法,这样就自动的将生成的类add到APP_LIKE_LIST中

    在编译transformClassesWithLifeCycleTransformForXXX时会运行transformer

    2. 通信

    页面跳转

    这个比较简单,可以使用ARouter类似框架

    组件相互调用

    第一种方式可以用接口下沉方式,即组件面向接口编程,并将相关接口下沉到基础库中
    第二种可以使用依赖隔离SPI,如ServiceLoader方式:《WMRouter:美团外卖Android开源路由框架

    同上述:

    1. 定义Annotation

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface IServiceLoader {
    
        Class[] interfaces();
    
        String[] key() default {};
    
        boolean singleton() default false;
    
        boolean defaultImpl() default false;
    }
    

    2.定义 Processor

      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        if (env.processingOver()) {
          generateInitClass();
        } else {
          processAnnotations(env);
        } 
        return true;
      }
      
      private void processAnnotations(RoundEnvironment env) {
        for (Element element : env.getElementsAnnotatedWith((Class)IServiceLoader.class)) {
        ...
          IServiceLoader service = (IServiceLoader)cls.getAnnotation(IServiceLoader.class);
         ...
          List<? extends TypeMirror> typeMirrors = getInterface(service);
          String[] keys = service.key();
          String implementationName = cls.className();
          boolean singleton = service.singleton();
          boolean defaultImpl = service.defaultImpl();
          if (typeMirrors != null && !typeMirrors.isEmpty())
            for (TypeMirror mirror : typeMirrors) {
            ...
              String interfaceName = getClassName(mirror);
              Entity entity = this.mEntityMap.get(interfaceName);
             ...
              if (defaultImpl)
                entity.put("_service_default_impl", implementationName, singleton); 
              ...
              entity.put(null, implementationName, singleton);
            }  
        } 
      }
      
        private void generateInitClass() { 
        BaseProcessor.ServiceInitClassBuilder generator = new BaseProcessor.ServiceInitClassBuilder(this, "ServiceInit_" + this.mHash);
        ...
        MethodSpec methodSpec = MethodSpec.methodBuilder("init").addModifiers(new Modifier[] { Modifier.PUBLIC, Modifier.STATIC }).returns(TypeName.VOID).addCode(this.builder.build()).build();
          TypeSpec typeSpec = TypeSpec.classBuilder(this.className).addModifiers(new Modifier[] { Modifier.PUBLIC }).addMethod(methodSpec).build();
    JavaFile.builder("xxx.serviceloader.api.generated.service", typeSpec)
              .build()
              .writeTo(BaseProcessor.this.filer);
      }
    

    3.集成

    implementation xxx:annotation:1.0.0'
    annotationProcessor xxx:processor:1.0.0'
    
    @IServiceLoader(interfaces = ILogin.class, singleton = true, defaultImpl = true)
    public class LoginService implements ILogin {
    }
    
    @IServiceLoader(interfaces = IVoiceAction.class, singleton = true, defaultImpl = true)
    public class VoiceServiceDelegate implements IVoiceAction {
    }
    
    

    最终生成的代码:

    public class ServiceInit_9eae46475978548480e0cd038d5e2430 {
      public static void init() {
          ServiceLoader.put(ILogin.class, "xxx.login.LoginService", LoginService.class, true);
        ServiceLoader.put(ILogin.class, "_service_default_impl", LoginService.class, true);
            ServiceLoader.put(IVoiceAction.class, "xxx.service.VoiceServiceDelegate", VoiceServiceDelegate.class, true);
        ServiceLoader.put(IVoiceAction.class, "_service_default_impl", VoiceServiceDelegate.class, true);
      }
    }
    

    如何使用呢?
    比如LoginService是写在业务moduleLogin中的,在业务moduleA中需要使用LoginService功能的话:

    if(ServiceLoaderHelper.getService(ILogin.class).isLogin()) {
    ...
    }
    

    4.植入代码

    public class WMRouterTransform extends Transform {
        @Override
        public void transform(TransformInvocation invocation) {
        
                ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
                ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, writer) {
                };
                String className = Const.SERVICE_LOADER_INIT.replace('.', '/');
                cv.visit(50, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
    
                MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                        Const.INIT_METHOD, "()V", null, null);
    
                mv.visitCode();
    
                for (String clazz : classes) {
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazz.replace('.', '/'),
                            "init",
                            "()V",
                            false);
                }
                mv.visitMaxs(0, 0);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitEnd();
                cv.visitEnd();
        }
    }
    

    生成出来的generated/ServiceLoaderInit:

    public class ServiceLoaderInit {
        public static void init() {
            ServiceInit_9eae46475978548480e0cd038d5e2430.init();
            ServiceInit_702c043c7b0baaf1dc41e5e42175fd7a.init();
            ServiceInit_62da7ecf45497e18e95e1cbb6dcb5661.init();
            ServiceInit_87ba86f99d83868774678286246a0c2d.init();
            ServiceInit_8a2d12381aef6adc21fbc1aa1d982ab9.init();
        }
    }
    

    在壳app初始化时反射调用ServiceLoaderInit的init方法即可

    3. 其他问题

    基础库和SDK版本号统一

    在主项目最外层用一个config.xml文件来统一管理基础库和sdk的版本

    组件之间资源名冲突

    在项目中制定资源文件命名规范,比如app_radio组件所有资源以radio_开头,所有开发人员必须遵守规范。

    android {
        resourcePrefix 'lg_'
    }
    

    相关文章

      网友评论

          本文标题:Android组件化

          本文链接:https://www.haomeiwen.com/subject/zromfjtx.html