Android組件化框架項目詳解


簡介

什么是組件化?

項目發展到一定階段時,隨着需求的增加以及頻繁地變更,項目會越來越大,代碼變得越來越臃腫,耦合會越來越多,開發效率也會降低,這個時候我們就需要對舊項目進行重構即模塊的拆分,官方的說法就是組件化。

組件化帶來的好處

那么,采用組件化能帶來什么好處呢?主要有以下兩點: 
1、現在Android項目中代碼量達到一定程度,編譯將是一件非常痛苦的事情,一般都需要編譯5到6分鍾。Android Studio 推出 instant run 由於各種缺陷和限制條件(比如采用熱修復tinker)一般情況下是被關閉的。而組件化框架可以使模塊單獨編譯調試,可以有效地減少編譯的時間。

2、通過組件化可以更好的進行並行開發,因為我們可以為每一個模塊進行單獨的版本控制,甚至每一個模塊的負責人可以選擇自己的設計架構而不影響其他模塊的開發,與此同時組件化還可以避免模塊之間的交叉依賴,每一個模塊的開發人員可以對自己的模塊進行獨立測試,獨立編譯和運行,甚至可以實現單獨的部署。從而極大的提高了並行開發效率。

組件化框架

來看組件化一個簡單的例子,圖例如下: 
這里寫圖片描述

這里寫圖片描述

基類庫的封裝

這里寫圖片描述

對於Android中常用的基類庫,主要包括開發常用的一些框架。

1、網絡請求(多任務下載和上傳,采用 Retrofit+RxJava 框架) 
2、圖片加載(策略模式,Glide 與 Picasso 之間可以切換) 
3、通信機制(RxBus) 
4、基類 adapter 的封裝(支持 item動畫、多布局item、下拉和加載更多、item點擊事件) 
5、基類 RecyclerView 的封裝(支持原生風格的下拉加載,item側滑等) 
6、mvp 框架 
7、各組件的數據庫實體類 
8、通用的工具類 
9、自定義view(包括對話框,ToolBar布局,圓形圖片等view的自定義) 
10、dagger 的封裝(用於初始化全局的變量和網絡請求等配置) 
11、其他等等

組件模式和集成模式切換的實現

music組件 下的 build.gradle 文件,其他組件類似。

/控制組件模式和集成模式
if (rootProject.ext.isAlone) { 
    apply plugin: 'com.android.application'
} else { 
    apply plugin: 'com.android.library' } 
apply plugin: 'com.neenbedankt.android-apt'

android { 
    compileSdkVersion rootProject.ext.android.compileSdkVersion 
    buildToolsVersion rootProject.ext.android.buildToolsVersion 
    defaultConfig { 
        if (rootProject.ext.isAlone) { 
            //組件模式下設置applicationId 
            applicationId "com.example.cootek.music" 
        } 
        minSdkVersion rootProject.ext.android.minSdkVersion 
        targetSdkVersion rootProject.ext.android.targetSdkVersion 
        versionCode rootProject.ext.android.versionCode 
        versionName rootProject.ext.android.versionName 
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 
        if (!rootProject.ext.isAlone) { 
            //集成模式下Arouter的配置,用於組件間通信的實現 
            javaCompileOptions { 
                annotationProcessorOptions { 
                    arguments = [moduleName: project.getName()] 
                } 
            } 
        } 
    } 
    buildTypes { 
        release { 
            minifyEnabled false 
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 
        } 
    } 
    compileOptions { 
        sourceCompatibility JavaVersion.VERSION_1_7 
        targetCompatibility JavaVersion.VERSION_1_7 
    } 
    sourceSets { 
        main { 
            //控制兩種模式下的資源和代碼配置情況 
            if (rootProject.ext.isAlone) { 
                manifest.srcFile 'src/main/module/AndroidManifest.xml' 
                java.srcDirs = ['src/main/java', 'src/main/module/java'] 
                res.srcDirs = ['src/main/res', 'src/main/module/res'] 
            } else { 
                manifest.srcFile 'src/main/AndroidManifest.xml' 
            } 
        } 
    } 
} 

dependencies { 
    compile fileTree(dir: 'libs', include: ['*.jar']) 
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 
        exclude group: 'com.android.support', module: 'support-annotations' 
    }) 
    //依賴基類庫 
    compile project(':commonlibrary') 
    //用作顏色選擇器 
    compile 'com.afollestad.material-dialogs:commons:0.9.1.0' 
    apt rootProject.ext.dependencies.dagger2_compiler 
    if (!rootProject.ext.isAlone) { 
    //集成模式下需要編譯器生成路由通信的代碼 
        apt rootProject.ext.dependencies.arouter_compiler 
    } 
    testCompile 'junit:junit:4.12'
}

為了區分集成模式和組件模式,我們使用isAlone變量來控制。

集成模式

1、首先需要在 config.gradle 文件中設置 isAlone = false。形如:

ext { 
    isAlone = false;   //false:作為Lib組件存在,true:作為application存在

2、然后 Sync 下。 
3、最后選擇 app 運行即可。

組件模式

1、首先需要在 config.gradle 文件中設置 isAlone = true 
2、然后 Sync 下。 
3、最后相應的模塊(new、chat、live、music、app)進行運行即可。

config.gradle 文件的配置情況如下:

ext { 
    isAlone = false;//false:作為集成模式存在,true:作為組件模式存在 

    // 各個組件版本號的統一管理 
    android = [ 
            compileSdkVersion: 24, 
            buildToolsVersion: "25.0.2", 
            minSdkVersion    : 16, 
            targetSdkVersion : 22, 
            versionCode      : 1, 
            versionName      : '1.0.0', 
    ] 

    libsVersion = [ 
            // 第三方庫版本號的管理 
            supportLibraryVersion = "25.3.0", 
            retrofitVersion = "2.1.0", 
            glideVersion = "3.7.0", 
            loggerVersion = "1.15", 
            // eventbusVersion = "3.0.0", 
            gsonVersion = "2.8.0", 
            butterknife = "8.8.0", 
            retrofit = "2.3.0", 
            rxjava = "2.1.1", 
            rxjava_android = "2.0.1", 
            rxlifecycle = "2.1.0", 
            rxlifecycle_components = "2.1.0", 
            dagger_compiler = "2.11", 
            dagger = "2.11", 
            greenDao = "3.2.2", 
            arouter_api = "1.2.2", 
            arouter_compiler = "1.1.3", 
            transformations = "2.0.2", 
            rxjava_adapter = "2.3.0", 
            gson_converter = "2.3.0", 
            scalars_converter = "2.3.0", 
            rxpermission = "0.9.4", 
            eventbus="3.0.0", 
            support_v4="25.4.0", 
            okhttp3="3.8.1" 
    ] 

    // 依賴庫管理 
    dependencies = [ 
            appcompatV7               : "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion", 
            design                    : "com.android.support:design:$rootProject.supportLibraryVersion", 
            cardview                  : "com.android.support:cardview-v7:$rootProject.supportLibraryVersion", 
            palette                   : "com.android.support:palette-v7:$rootProject.supportLibraryVersion", 
            recycleview               : "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion", 
            support_v4                : "com.android.support:support-v4:$rootProject.support_v4", 
            annotations               : "com.android.support:support-annotations:$rootProject.supportLibraryVersion", 
            eventBus                  : "org.greenrobot:eventbus:$rootProject.eventbus", 
            glide                     : "com.github.bumptech.glide:glide:$rootProject.glideVersion", 
            gson                      : "com.google.code.gson:gson:$rootProject.gsonVersion", 
            logger                    : "com.orhanobut:logger:$rootProject.loggerVersion", 
            butterknife               : "com.jakewharton:butterknife:$rootProject.butterknife", 
            butterknife_compiler      : "com.jakewharton:butterknife-compiler:$rootProject.butterknife", 
            retrofit                  : "com.squareup.retrofit2:retrofit:$rootProject.retrofit", 
            okhttp3                   : "com.squareup.okhttp3:okhttp:$rootProject.retrofit", 
            retrofit_adapter_rxjava2  : "com.squareup.retrofit2:adapter-rxjava2:$rootProject.rxjava_adapter", 
            retrofit_converter_gson   : "com.squareup.retrofit2:converter-gson:$rootProject.gson_converter", 
            retrofit_converter_scalars: "com.squareup.retrofit2:converter-scalars:$rootProject.scalars_converter", 
            rxpermission              : "com.tbruyelle.rxpermissions2:rxpermissions:$rootProject.rxpermission@aar", 
            rxjava2                   : "io.reactivex.rxjava2:rxjava:$rootProject.rxjava", 
            rxjava2_android           : "io.reactivex.rxjava2:rxandroid:$rootProject.rxjava_android", 
            rxlifecycle2              : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle", 
            rxlifecycle2_components   : "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.rxlifecycle_components", 
            dagger2_compiler          : "com.google.dagger:dagger-compiler:$rootProject.dagger_compiler", 
            dagger2                   : "com.google.dagger:dagger:$rootProject.dagger", 
            greenDao                  : "org.greenrobot:greendao:$rootProject.greenDao", 
            transformations           : "jp.wasabeef:glide-transformations:$rootProject.transformations", 
            //路由通訊 
            arouter_api               : "com.alibaba:arouter-api:$rootProject.arouter_api", 
            arouter_compiler          : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler" 
    ] 
}

組件間通信實現

組件間通信的實現可以使用阿里開源的 Arouter 路由通信。相關內容可以查看:https://github.com/alibaba/ARouter。 
首先,初始化所有的數據信息。

private List<MainItemBean> getDefaultData() { 
    List<MainItemBean> result = new ArrayList<>(); 
    MainItemBean mainItemBean = new MainItemBean(); 
    mainItemBean.setName("校園"); 
    mainItemBean.setPath("/news/main"); 
    mainItemBean.setResId(R.mipmap.ic_launcher); 
    MainItemBean music=new MainItemBean(); 
    music.setName("音樂"); 
    music.setResId(R.mipmap.ic_launcher); 
    music.setPath("/music/main"); 
    MainItemBean live = new MainItemBean(); 
    live.setName("直播"); 
    live.setResId(R.mipmap.ic_launcher); 
    live.setPath("/live/main"); 
    MainItemBean chat = new MainItemBean(); 
    chat.setName("聊天"); 
    chat.setPath("/chat/splash"); 
    chat.setResId(R.mipmap.ic_launcher); 
    result.add(mainItemBean); 
    result.add(music); 
    result.add(live); 
    result.add(chat); 
    return result; 
}

然后在設置每個 item 的點擊事件時,啟動組件界面跳轉。

@Override
public void onItemClick(int position, View view) { 
    MainItemBean item=mainAdapter.getData(position); 
    ARouter.getInstance().build(item.getPath()).navigation(); 
}

每個組件入口界面的設置(比如直播 Live 組件,其它組件類似)。

@Route(path = "/live/main") 
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {
//
}

res資源和AndroidManifest配置

我們通過判斷組件處於哪種模式來動態設置項目res資源和Manifest、以及代碼的位置。以直播組件為例,其它組件類似。

這里寫圖片描述 
作為一個組件模塊后,再來看一下直播組件的 build.gradle 文件對代碼資源等位置的配置。

sourceSets { 
    main { 
        if (rootProject.ext.isAlone) { 
            manifest.srcFile 'src/main/module/AndroidManifest.xml' 
            java.srcDirs = ['src/main/java', 'src/main/module/java'] 
            res.srcDirs = ['src/main/res', 'src/main/module/res'] 
        } else { 
            manifest.srcFile 'src/main/AndroidManifest.xml' 
        } 
    } 
}

全局application的實現和數據的初始化

采用類似於 Glide 在 Manifest 初始化配置的方式來初始化各個組件的 Application,下面以直播組件為例來完成初始化,其它類似。 
在 BaseApplication 中,初始化 ApplicationDelegate 代理類。

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

ApplicationDelegate 內部是怎樣的呢,看一段源碼。

public class ApplicationDelegate implements IAppLife { 
    private List<IModuleConfig> list; 
    private List<IAppLife> appLifes; 
    private List<Application.ActivityLifecycleCallbacks> liferecycleCallbacks; 

    public ApplicationDelegate() { 
        appLifes = new ArrayList<>(); 
        liferecycleCallbacks = new ArrayList<>(); 
    } 

    @Override 
    public void attachBaseContext(Context base) { 
        //初始化Manifest文件解析器,用於解析組件在自己的Manifest文件配置的Application 
        ManifestParser manifestParser = new ManifestParser(base); 
        list = manifestParser.parse(); 
        //解析得到的組件Application列表之后,給每個組件Application注入 
        //context,和Application的生命周期的回調,用於實現application的同步 
        if (list != null && list.size() > 0) { 
            for (IModuleConfig configModule : 
                    list) { 
                configModule.injectAppLifecycle(base, appLifes); 
                configModule.injectActivityLifecycle(base, liferecycleCallbacks); 
            } 
        } 
        if (appLifes != null && appLifes.size() > 0) { 
            for (IAppLife life : 
                    appLifes) { 
                life.attachBaseContext(base); 
            } 
        } 
    } 

    @Override 
    public void onCreate(Application application) { 
        //相應調用組件Application代理類的onCreate方法 
        if (appLifes != null && appLifes.size() > 0) { 
            for (IAppLife life : 
                    appLifes) { 
                life.onCreate(application); 
            } 
        } 
        if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) { 
            for (Application.ActivityLifecycleCallbacks life : 
                    liferecycleCallbacks) { 
                application.registerActivityLifecycleCallbacks(life); 
            } 
        } 
    } 

    @Override 
    public void onTerminate(Application application) { 
        //相應調用組件Application代理類的onTerminate方法 
        if (appLifes != null && appLifes.size() > 0) { 
            for (IAppLife life : 
                    appLifes) { 
                life.onTerminate(application); 
            } 
        } 
        if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) { 
            for (Application.ActivityLifecycleCallbacks life : 
                    liferecycleCallbacks) { 
                application.unregisterActivityLifecycleCallbacks(life); 
            } 
        } 
    } 
}

組件 Manifest 中 application 的全局配置如下:

<meta-data 
    android:name="com.example.live.LiveApplication" 
    android:value="IModuleConfig" />

ManifestParser 會對其中 value 為 IModuleConfig 的 meta-data 進行解析,並通過反射生成實例。

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()) { 
                //會對其中value為IModuleConfig的meta-data進行解析,並通過反射生成實例 
                    if (MODULE_VALUE.equals(appInfo.metaData.get(key))) { 
                        modules.add(parseModule(key)); 
                    } 
                } 
            } 
        } catch (PackageManager.NameNotFoundException e) { 
            throw new RuntimeException("Unable to find metadata to parse IModuleConfig", e); 
        } 
        return modules; 
    } 

    //通過類名生成實例 
    private static IModuleConfig parseModule(String className) { 
        Class<?> clazz; 
        try { 
            clazz = Class.forName(className); 
        } catch (ClassNotFoundException e) { 
            throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e); 
        } 

        Object module; 
        try { 
            module = clazz.newInstance(); 
        } catch (InstantiationException e) { 
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e); 
        } catch (IllegalAccessException e) { 
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e); 
        } 

        if (!(module instanceof IModuleConfig)) { 
            throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module); 
        } 
        return (IModuleConfig) module; 
    } 
}

這樣通過以上步驟就可以在 Manifest 文件中配置自己組件的 Application,用於初始化組件內的數據,比如在直播組件中初始化 Dagger注解 的全局配置。

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()) { 
                //會對其中value為IModuleConfig的meta-data進行解析,並通過反射生成實例 
                    if (MODULE_VALUE.equals(appInfo.metaData.get(key))) { 
                        modules.add(parseModule(key)); 
                    } 
                } 
            } 
        } catch (PackageManager.NameNotFoundException e) { 
            throw new RuntimeException("Unable to find metadata to parse IModuleConfig", e); 
        } 
        return modules; 
    } 

    //通過類名生成實例 
    private static IModuleConfig parseModule(String className) { 
        Class<?> clazz; 
        try { 
            clazz = Class.forName(className); 
        } catch (ClassNotFoundException e) { 
            throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e); 
        } 

        Object module; 
        try { 
            module = clazz.newInstance(); 
        } catch (InstantiationException e) { 
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e); 
        } catch (IllegalAccessException e) { 
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e); 
        } 

        if (!(module instanceof IModuleConfig)) { 
            throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module); 
        } 
        return (IModuleConfig) module; 
    } 
}

這樣通過以上步驟就可以在 Manifest 文件中配置自己組件的 Application,用於初始化組件內的數據,比如在直播組件中初始化 Dagger注解 的全局配置。

public class LiveApplication implements IModuleConfig,IAppLife { 
    private static MainComponent mainComponent; 

    @Override 
    public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) { 
        //這里需要把本引用添加到Application的生命周期的回調中,以便實現回調 
        iAppLifes.add(this); 
    } 

    @Override 
    public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycleCallbackses) { 
    } 

    @Override 
    public void attachBaseContext(Context base) { 
    } 

    @Override 
    public void onCreate(Application application) { 
        //在onCreate方法中對Dagger進行初始化 
        mainComponent = DaggerMainComponent.builder().mainModule(new MainModule()) 
                              .appComponent(BaseApplication.getAppComponent()).build(); 
    } 

    @Override 
    public void onTerminate(Application application) { 
        if (mainComponent != null) { 
            mainComponent = null; 
        } 
    } 

    public static MainComponent getMainComponent() { 
        return mainComponent; 
    } 
}

組件內網絡請求和攔截器

由於每個組件的 BaseUrl 和網絡配置等可能不一樣,所以每個組件可以在自己配置的 dagger 中的 MainConponent 實現自己的網絡請求和攔截器。以直播為例,部分代碼內容如下:

MainComponent:

@PerApplication
@Component(dependencies = AppComponent.class, modules = MainModule.class) 
public interface MainComponent { 
    public DaoSession getDaoSession(); 

    public MainRepositoryManager getMainRepositoryManager(); 
}

MainModule部分代碼:

public class MainModule { 
    @Provides 
    @PerApplication 
    public MainRepositoryManager provideRepositoryManager(@Named("live") Retrofit retrofit, DaoSession daoSession) { 
        return new MainRepositoryManager(retrofit, daoSession); 
    } 
    @Provides 
    @Named("live") 
    @PerApplication 
    public Retrofit provideRetrofit(@Named("live") OkHttpClient okHttpClient,@Nullable Gson gson){ 
        Retrofit.Builder builder=new Retrofit.Builder().baseUrl(LiveUtil.BASE_URL).addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
                .addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient); 
        return builder.build(); 
    } 
    @Provides 
    @Named("live") 
    @PerApplication 
    public OkHttpClient provideOkHttpClient(@Named("live")LiveInterceptor interceptor){ 
        OkHttpClient.Builder builder=new OkHttpClient.Builder(); 
        builder.connectTimeout(10, TimeUnit.SECONDS).readTimeout(10,TimeUnit.SECONDS); 
        builder.addInterceptor(interceptor); 
        return builder.build(); 
    } 
    @Provides 
    @Named("live") 
    @PerApplication 
    public LiveInterceptor provideNewsInterceptor(){ 
        return new LiveInterceptor(); 
    } 
}

難點

在項目中使用組件化,可能會遇到很多問題,下面將問題羅列如下:

資源命名沖突

官方說法是在每個 module 的 build.gradle 文件中配置資源文件名前綴。

這種方法缺點就是,所有的資源名必須要以指定的字符串(moudle_prefix)做前綴,否則會異常報錯,而且這方法只限定xml里面的資源,對圖片資源並不起作用,所以圖片資源仍然需要手動去修改資源名。所以不是很推薦使用這種方法來解決資源名沖突。所以只能自己注意點,在創建資源的時候,盡量不讓其重復。例如:

resourcePrefix  "moudle_prefix"

butterKnife使用問題

雖然 Butterknife 支持在 lib 中使用,但是條件是用 R2 代替 R ,在組件模式和集成模式的切換中,R2<->R 之間的切換是無法完成轉換的,切換一次要改動全身,是非常麻煩的!所以不推薦在組件化中使用 Butterknife。

library重復依賴問題

相信這個問題,大家在平時的開發中都會遇到,所以我們需要將多余的包給排除出去。可以參考如下的配置:

dependencies { 
    compile fileTree(dir: 'libs', include: ['*.jar']) 
    testCompile 'junit:junit:4.12' 
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 
        exclude group: 'com.android.support', module: 'support-annotations' 
    }) 
    compile(rootProject.ext.dependencies.appcompatV7) { 
        exclude module: "support-v4" 
        exclude module: "support-annotations" 
    } 
    compile rootProject.ext.dependencies.recycleview 
    compile rootProject.ext.dependencies.design 

    compile(rootProject.ext.dependencies.support_v4) { 
       exclude module: "support-annotations" 
    } 
    compile rootProject.ext.dependencies.annotations 
    compile(rootProject.ext.dependencies.butterknife) { 
        exclude module: 'support-annotations' 
    } 
    compile rootProject.ext.dependencies.rxjava2 
    compile(rootProject.ext.dependencies.rxjava2_android) { 
        exclude module: "rxjava" 
    } 
    compile(rootProject.ext.dependencies.rxlifecycle2) { 
        exclude module: 'rxjava' 
        exclude module: 'jsr305' 
    } 
    compile(rootProject.ext.dependencies.rxlifecycle2_components) { 
        exclude module: 'support-v4' 
        exclude module: 'appcompat-v7' 
        exclude module: 'support-annotations' 
        exclude module: 'rxjava' 
        exclude module: 'rxandroid' 
        exclude module: 'rxlifecycle' 
    } 
    compile(rootProject.ext.dependencies.retrofit) { 
        exclude module: 'okhttp' 
        exclude module: 'okio' 
    } 
    compile(rootProject.ext.dependencies.retrofit_converter_gson) { 
        exclude module: 'gson' 
        exclude module: 'okhttp' 
        exclude module: 'okio' 
        exclude module: 'retrofit' 
    } 
    compile(rootProject.ext.dependencies.retrofit_adapter_rxjava2) { 
        exclude module: 'rxjava' 
        exclude module: 'okhttp' 
        exclude module: 'retrofit' 
        exclude module: 'okio' 
    } 
    compile rootProject.ext.dependencies.greenDao 
    compile rootProject.ext.dependencies.okhttp3 
    compile rootProject.ext.dependencies.gson 
    compile rootProject.ext.dependencies.glide 
    compile rootProject.ext.dependencies.eventBus 
    compile rootProject.ext.dependencies.dagger2 
    compile(rootProject.ext.dependencies.rxpermission) { 
        exclude module: 'rxjava' 
    } 
    compile rootProject.ext.dependencies.retrofit_converter_scalars 
    annotationProcessor rootProject.ext.dependencies.dagger2_compiler 
    annotationProcessor rootProject.ext.dependencies.butterknife_compiler 
    compile rootProject.ext.dependencies.butterknife 
    compile rootProject.ext.dependencies.transformations 
    compile rootProject.ext.dependencies.arouter_api 
}

附:項目實例

聊天模塊

優秀項目參考: 
MVPArms 
https://github.com/JessYanCoding/MVPArms

全民直播 
https://github.com/jenly1314/KingTV

音樂項目 
https://github.com/hefuyicoder/ListenerMusicPlayer 
https://github.com/aa112901/remusic

大象:PHPHub客戶端 
https://github.com/Freelander/Elephant

MvpApp 
https://github.com/Rukey7/MvpApp

CloudReader 
https://github.com/youlookwhat/CloudReader


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM