美文网首页
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