下载.png组件指的是单一的功能组件,如 [视频组件]、[支付组件] 等,每个组件都可以以一个单独的 module 开发,并且可以单独抽出来作为 SDK 对外发布使用。
每个组件都是一个完整的整体,所以组件开发过程中要满足单独运行及调试的要求,这样还可以提升开发过程中项目的编译速度。
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 文件,在两个文件中就可以分别配置单独调试和集成调试时的不同的配置,如图:
// 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_'
}
网友评论