Android组件化搭建

什么是组件化

为了降低项目耦合性,在Android工程中如何实施,目前有两种途径,也是两大流派,一个是组件化,一个是插件化。在这里只介绍组件化,插件化暂不介绍 正常的APP只有一个application的module和多个libary的module,而组件化开发是为了多个module都可以是application单独运行,发布时在合并。

代码解耦

APP:作为主的application的入口,主要作用是了初始化项目的第三方库、欢迎页和主页。

组件:拆分的业务模块,这种module是一个完整的功能模块,组件可以有多个,具体划分根据业务情况进行划分。

公用库:基础库library,这些代码被其他组件直接引用,也可以创建一个module专门放资源的库。

模块通信

目前使用的是阿里的ARouter进行模块的通信,使用URL进行跳转,达到主项目和组件、组件与组件之间不能直接使用类的相互引用来进行数据交互效果。详细使用,请自行查看github项目或者google,这里只介绍简单介绍:

1、添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

2、初始化SDK
if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

3、发起路由操作
ARouter.getInstance().build("/test/activity").navigation();
详细使用

1、在global.gradle定义一个参数

ext {
    isLibrary = true //false:application;true:library
}

注:global.gradle是全局定义版本库文件

2、在各业务组件module的build.gradle

if (rootProject.ext.isLibrary) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

android {
    defaultConfig {
        if (!rootProject.ext.isLibrary) {
            applicationId "自己的applicationId"
        }
    }

    sourceSets {
        main {
            if (rootProject.ext.isLibrary) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            }
        }
    }
}

3、当业务module当成一个library,需要实现原先Application类中功能,可以使用委托方式实现。

a、先定义一个IModuleConfig接口,主要实现app生命周期的管理和activity回收管理

public interface IModuleConfig {
    void injectAppLifecycle(Context context, List<IAppLife> lifeList);

    void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> callbacksList);

}

b、定义IAppLife接口,实现Application相关类功能

public interface IAppLife {
    void attachBaseContext(Context base);

    void onCreate(BaseApp application);

    void onTerminate(BaseApp application);

    void onTrimMemory(int level);

    void onLowMemory();
}

c、在业务module的library里的AndroidManifest.xml配置IModuleConfig

<meta-data
        android:name="业务模块包名的实现Application类"
        android:value="IModuleConfig" />

d、解析配置IModuleConfig业务module的Application类

public final class ManifestParser {
    private static final String MODULE_VALUE = "IModuleConfig";

    private final Context context;

    public ManifestParser(Context context) {
        this.context = context;
    }

    public List<IModuleConfig> parse() {
        List<IModuleConfig> modules = new ArrayList<>();
        try {
            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
            if (appInfo.metaData != null) {
                for (String key : appInfo.metaData.keySet()) {
                    if (MODULE_VALUE.equals(appInfo.metaData.get(key))) {
                        modules.add(parseModule(key));
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException("解析Application失败", e);
        }

        return modules;
    }

    private static IModuleConfig parseModule(String className) {
        Class<?> clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }

        Object module;
        try {
            module = clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return (IModuleConfig) module;
    }
}

e、实现IAppLife的具体类

public class ApplicationDelegate implements IAppLife {
    private List<IModuleConfig> configList;
    private List<IAppLife> appLifeList;
    private List<Application.ActivityLifecycleCallbacks> callbacksList;

    public ApplicationDelegate() {
        appLifeList = new ArrayList<>();
        callbacksList = new ArrayList<>();
    }

    @Override
    public void attachBaseContext(Context base) {
        ManifestParser manifestParser = new ManifestParser(base);
        configList = manifestParser.parse();
        if (configList != null && configList.size() > 0) {
            for (IModuleConfig configModule : configList) {
                configModule.injectAppLifecycle(base, appLifeList);
                configModule.injectActivityLifecycle(base, callbacksList);
            }
        }
        if (appLifeList != null && appLifeList.size() > 0) {
            for (IAppLife life : appLifeList) {
                life.attachBaseContext(base);
            }
        }
    }

    @Override
    public void onCreate(BaseApp application) {
        Ln.d("ApplicationDelegate onCreate:%s", appLifeList.size());
        if (appLifeList != null && appLifeList.size() > 0) {
            for (IAppLife life : appLifeList) {
                life.onCreate(application);
            }
        }
        if (callbacksList != null && callbacksList.size() > 0) {
            for (Application.ActivityLifecycleCallbacks life : callbacksList) {
                application.registerActivityLifecycleCallbacks(life);
            }
        }
    }

    @Override
    public void onTerminate(BaseApp application) {
        if (appLifeList != null && appLifeList.size() > 0) {
            for (IAppLife life : appLifeList) {
                life.onTerminate(application);
            }
        }
        if (callbacksList != null && callbacksList.size() > 0) {
            for (Application.ActivityLifecycleCallbacks life : callbacksList) {
                application.unregisterActivityLifecycleCallbacks(life);
            }
        }
    }

    @Override
    public void onTrimMemory(int level) {
        if (appLifeList != null && appLifeList.size() > 0) {
            for (IAppLife life : appLifeList) {
                life.onTrimMemory(level);
            }
        }
    }

    @Override
    public void onLowMemory() {
        if (appLifeList != null && appLifeList.size() > 0) {
            for (IAppLife life : appLifeList) {
                life.onLowMemory();
            }
        }
    }
}

f、公用的Application类

public class BaseApplication extends BaseApp {
    private static BaseApplication instance;
    private ApplicationDelegate applicationDelegate;

    public static BaseApplication getInstance() {
        return instance;
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Ln.init(this);
        MultiDex.install(this);
        applicationDelegate = new ApplicationDelegate();
        applicationDelegate.attachBaseContext(base);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (!HostUtils.isInMainProcess(this)) return;
        // 这两行必须写在init之前,否则这些配置在init过程中将无效
        ARouter.openLog();     // 打印日志
        Ln.d("BaseApplication:%s", isDebug);
        ARouter.openDebug();
        ARouter.init(this);
        instance = this;
        applicationDelegate.onCreate(this);
        Ln.d("BaseApplication applicationDelegate.");
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        applicationDelegate.onTerminate(this);
    }

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

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

注:module1 和 module2 分别都依赖了 common,不会导致 library 重复依赖。

module为library的填坑

1、资源ID报无法访问的错误,因为在library中,资源ID是一个变量,而不是常量,在switch中、ButterKnife、注解使用资源ID都会提示错误。switch报错解决办法是用if-else,而注解解决办法使用string参数,注解实现类中根据这个string参数读取资源。

2、使用Dagger2后,无法自动生成相关的Dagger类,解决办法:

定义一个注解
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerApplication {
}

在Dagger接口类上加上注解
@PerApplication
@Component(modules = {NetworkModule.class})
@Singleton
public interface AppComponent {

}

3、资源冲突问题,当分别开发模块时,容易出资源重复命名的问题,可以在build.gradle中设置

resourcePrefix "module1_"
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • Android组件化项目地址:Android组件化项目AndroidModulePattern Android组件...
    半灬边灬天阅读 2,920评论 4 37
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,710评论 2 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,988评论 25 707
  • 最有名的龙井茶产于杭州西湖一带,这种茶叶泡开后,颀长舒展,汤色碧绿、清香扑鼻,与人们脑海中的西子.与龙井联系最密切...
    潘潘的成长之路阅读 352评论 0 0
  • 1.所有组件(component)都有一个 style 属性; 2.在一个地方使用 StyleSheet.Crea...
    falcon_li阅读 215评论 0 0