主頁 > 移動端開發 > 安卓一步一步搭建組件化

安卓一步一步搭建組件化

2020-11-12 13:27:06 移動端開發

安卓組件化的搭建和基本功能的實作

  • 1.組件化是什么?
    • 1.1 了解組件化:
    • 1.2 組件化的基本結構:
    • 1.3 組件化的優點:
  • 2.組件化框架的搭建:
    • 2.1 第一步:搭建基礎層
      • 2.1.1 創建config.gradle
      • 2.1.2 建立一個library模塊作為基礎層
      • 2.1.3 所有模塊都要中都要添加基礎層模塊的依賴
    • 2.2 第二步:搭建組件層,
      • 2.2.1 為組件層模塊創建不同的Manifest表單
    • 2.3 第三步:搭建app層
    • 2.4 檢驗搭建成果
  • 3. 組件間的activity等界面跳轉
    • 3.1 ARouter介紹
    • 3.2 使用ARouter的準備
    • 3.3 ARouter的使用
  • 4. 組件之間的資料傳遞之(介面+實作)
    • 4.1 (介面+實作)的大概思路
    • 4.2 (介面+實作)的實操
  • 5. 組件之間的資料傳遞之(EventBus)
    • 5.1 什么是EventBus?
    • 5.2 使用EventBus的準備作業
    • 5.3 EventBus的基本使用
      • 5.3.1 訂閱事件
      • 5.3.2 發布事件
    • 5.4 EventBus的進階使用
      • 5.4.1 EventBus的執行緒模式
      • 5.4.2 EventBus的黏性事件
  • 6.Applicaion的動態配置
    • 6.1 建立一個BaseApplication
    • 6.2 修改主Module的application類
    • 6.3 修改Login組件的application類
    • 6.4 運用反射獲取其他組件的application進行初始化
  • 7.主專案使用各個組件的類的方法
    • 7.1 反射的方法
    • 7.2 介面+實作的方法
    • 7.3 路由的實作方法

1.組件化是什么?

1.1 了解組件化:

  • 在沒有接觸組件化之前,我們寫安卓專案的程序中,所有布局,邏輯功能的都是在app模塊下實作,這種方式寫一些小專案是很方便的,但是有些真正有用處的手機軟體都是由團隊來協作完成的,由于功能的繁雜,如果多個開發人員都在app一個模塊中進行功能開發,代碼就會顯得很臃腫,所以為了更好的更有效率的分工,組件化功能的實作必不可少,

1.2 組件化的基本結構:

  • 組件化大致分為3個結構:基礎層組件層應用層,下面我們具體說一說著三個結構,
  1. 基礎層:基礎層,就是由我們手動在這里去包含所有專案中想用到的基本庫的依賴,然后在其他所有組件中依賴于這個Base基礎層,就可以在專案的任何地方應用到我們添加的依賴,這樣不僅只需要開發一套依賴庫的代碼,還解耦了基礎功能和業務功能的耦合,在基礎庫變更時更加容易操作,
  2. 組件層:組件層可以理解為業務層,比如一個聊天軟體專案,其中分為登錄,聊天,查詢等功能,組件層就是把這些功能進行一個一個的分離出來,每一個功能對應一個特定的組件,
  3. 應用層:應用層就是app,在未接觸組件化之前,我們所有的邏輯,布局都在app里面寫,而組件化中,app可以形容為一個空殼,它其中依賴包含了我們創建的其他組件,它會根據設定按照需要參考不同的模塊,

1.3 組件化的優點:

  • 經過剛剛的介紹,我們對組件化有了大致的認識,想必他的有點我們也清楚了,就是方便多人開發,互不影響,自己開發的組件可以單獨運行,不受外界限制,不至于因為其他模塊出現問題而導致自己模塊不能運行的情況,另外,在一個很龐大的專案需要修改時,也可以很方便的找到要修改的模塊,不必要去在龐大的代碼中尋找,

2.組件化框架的搭建:

  • 在說搭建組件化之前,先介紹一下AndroidStudio開發Android專案時常用到的兩種插件,
  • application插件:如果一個模塊被宣告為aoolication,那么它會成為一個apk檔案,是可以直接安裝運行的專案,
  • library插件:被宣告為library的模塊,它會成為一個aar檔案,不可以單獨運行,

2.1 第一步:搭建基礎層

2.1.1 創建config.gradle

在這里插入圖片描述

  • 在這里面,我們寫上所有依賴庫,以及專案中sdk等等的版本號,然后直接讓buil.gradle去apply它,之后有什么更改就可以直接在這里面改,比如版本號升級等問題,以下是我在其中添加的代碼:
ext{

    android = [
            compileSdkVersion :30,
            buildToolsVersion: "30.0.2",
            applicationId :"activitytest.com.example.moduletest",
            minSdkVersion: 29,
            targetSdkVersion :30,
            versionCode :1,
            versionName :"1.0",
    ]

    androidxDeps = [
            "appcompat": 'androidx.appcompat:appcompat:1.1.0',
            "material": 'com.google.android.material:material:1.1.0',
            "constaraintlayout": 'androidx.constraintlayout:constraintlayout:1.1.3',
    ]

    commonDeps = [
            "arouter_api"          : 'com.alibaba:arouter-api:1.5.1',
            "glide"                : 'com.github.bumptech.glide:glide:4.11.0'

    ]

    annotationDeps = [
            "arouter_compiler" : 'com.alibaba:arouter-compiler:1.5.1'
    ]

    retrofitDeps = [
            "retrofit"  : 'com.squareup.retrofit2:retrofit:2.9.0',
            "converter" : 'com.squareup.retrofit2:converter-gson:2.9.0',
            "rxjava"    : 'io.reactivex.rxjava2:rxjava:2.2.20',
            "rxandroid" : 'io.reactivex.rxjava2:rxandroid:2.1.1',
            "adapter"   : 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    ]

    androidxLibs = androidxDeps.values()
    commonLibs = commonDeps.values()
    annotationLibs = annotationDeps.values()
    retrofitLibs = retrofitDeps.values()
}
  • 在這里我寫了一些版本號,support庫,路由,圖片加載等常用的基礎庫,最后面的這4行,是對前面的一個封裝,在其他地方呼叫這4行,相當于呼叫了我在這里寫的所有代碼,這里的具體知識請看使用config.gradle統一管理專案的依賴庫
  • 之后在專案目錄下的build.gradle中apply操作,
    在這里插入圖片描述

2.1.2 建立一個library模塊作為基礎層

  1. 點擊file->new->new module,選擇library module模塊,我在這里命名為Baselibs,
  2. 在模塊的build.gradle中,添加我們剛剛寫的依賴,注意這里運用關鍵字api來添加,因為這樣做,別的模塊繼承當前模塊時,不必再單獨寫依賴了,
dependencies {

    api rootProject.ext.androidxLibs
    api rootProject.ext.commonLibs
    api rootProject.ext.annotationLibs
    testImplementation 'junit:junit:4.+'
    api 'org.greenrobot:eventbus:3.1.1'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2.1.3 所有模塊都要中都要添加基礎層模塊的依賴

  • 這里以app層為例:在app模塊中的build.gradle中添加如下,
dependencies {
    implementation project(":Baselibs")
}

就這樣,基礎層就建立完成了,

2.2 第二步:搭建組件層,

  • 由于組件層根據集成開發和組件開發兩種不同的情況,要在application和library之間來回切換,所以我們還是先創建一個模塊,這里我建立一個login模塊,就在login的build.gradle中,我們要動態的規定是application還是library,
  • 由于專案目錄下的gradle.properties中的變數,可以在build.gradle中參考,所以在gradle.preperties中設定一個isModule變數:
# true時為組件化模式開發,false時為集成模式開發
isModule = false
  • 然后在組件中的build.gradle中根據isModule的值來進行設定:
if(isModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}
  • 不要忘記添加基礎庫的依賴:
dependencies {
    implementation project(":Baselibs")
 }

2.2.1 為組件層模塊創建不同的Manifest表單

  • 因為一個專案中只能有一個application,所以我們要在組件開發時,令login模塊應用存在application的表單,在集成開發時,令login模塊應用不存在application的Manifest表單,
  1. 在login/src/main目錄下創建module檔案,在這里面創建一個Manifest表單,書寫集成開發情況下的表單,
  • login/src/main/module目錄下的:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="activitytest.com.example.login">

    <application
        android:allowBackup="true">
        <activity android:name=".Login"></activity>
    </application>

</manifest>
  • login/src/main目錄下的:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="activitytest.com.example.login">

    <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/Theme.ModuleTest">
        <activity android:name=".Login">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 這里的區別就不說了,
  1. 一個專案里只有一個application,那么也就是說一個專案中也只有一個applicationId,applicationId和表單的選擇都是在build.gradle中設定的,所以我們還要根據isModule的值,來設定組件中是否存在applicationId,以及應用哪一個Maniest.xml,
defaultConfig {

    if(isModule.toBoolean()){
        applicationId "activitytest.com.example.login"
    }
 }
sourceSets{
    main{
        if(isModule.toBoolean()){
            manifest.srcFile 'src/main/AndroidManifest.xml'
        }else{
            manifest.srcFile 'src/main/module/AndroidManifest.xml'
        }
    }
}
  • 按照如上所述方法,我又創建了一個share組件和一個mine組件,之后的內容會用到,

2.3 第三步:搭建app層

  • 這一步就比較簡單,只需要在app模塊的build.gradle中根據isModule的值來決定是否添加其他組件層的模塊就好了,
dependencies {
    implementation project(":Baselibs")

    if(!isModule.toBoolean()){
        implementation project(":login")
        implementation project(":share")
        implementation project(":mine")
    }
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2.4 檢驗搭建成果

  • 當isModule的值設定為false時,代表此時為集成開發,只有app組件可以運行,其他子業務組件都不可以單獨運行,
    在這里插入圖片描述

  • 當isModule的值為true時,代表此時為組件開發,所有組件都可以單獨運行,這時當你運行每一個組件的時候,主頁面會顯示你運行組件的頁面,
    在這里插入圖片描述
    注:每次修改isModule的值和修改build.gradle時,都要點擊一下sync,

  • 這樣一個簡單的組件化框架就搭建完了,但是組件之間也要有通信交流的功能,這些知識請往下看,

3. 組件間的activity等界面跳轉

  • 在我們沒有應用組件化開發之前,界面跳轉我們主要用顯式Intent和隱式Intent兩種方法,但是組件化開發中,是不允許組件層的模塊橫向依賴的,所以不可以直接訪問彼此的類,就不能用顯式Intent來跳轉,而用隱式Intent跳轉還需要通過 AndroidManifest 集中管理,在協作開發就比較麻煩,所以在這里介紹一個界面跳轉的新方法,使用 Alibaba 開源的 ARouter 來實作,

3.1 ARouter介紹

  • ARouter是一個用于幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦,它可以實作組件間的路由功能,路由是指從一個介面上收到資料包,根據資料路由包的目的地址進行定向并轉發到另一個介面的程序,這里可以體現出路由跳轉的特點,非常適合組件化解耦,

3.2 使用ARouter的準備

  1. 要使用 ARouter 進行界面跳轉,需要我們的組件對 Arouter 添加依賴,因為所有的組件都依賴了基礎層Baselibs模塊,Baselibs模塊又依賴了我們寫的config.gradle,所以我們在config.gradle模塊中添加 ARouter 的依賴即可,
    在這里插入圖片描述
  2. 除了添加ARouter的依賴,我們還要添加注釋處理器依賴,這個不同于ARouter依賴,這個需要在每一個應用的組件中都添加一次,不僅添加這個,還要在每個應用的組件的build.gradle的defaultConfig下配置ARouter,
dependencies {
    implementation project(":Baselibs")
    annotationProcessor rootProject.ext.annotationLibs		//注釋處理器

    if(!isModule.toBoolean()){
        implementation project(":login")
        implementation project(":share")
        implementation project(":mine")
    }
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
defaultConfig {
    javaCompileOptions{				//配置ARouter
        annotationProcessorOptions{
            includeCompileClasspath false
            arguments = [AROUTER_MODULE_NAME:project.getName()]
        }
    }
}
  1. 在使用之前,我們還要在app層的application中初始化ARouter,為了讓專案剛剛啟動就初始化,所以我們在application中的oncreate方法中初始化,
public class MainApplication extends BaseApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        //ARouter后臺有ILogger介面,定義了一些輸出日志
        if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init程序中將無效
            ARouter.openLog();     // 列印日志
            ARouter.openDebug();   // 開啟除錯模式(如果在InstantRun模式下運行,必須開啟除錯模式!線上版本需要關閉,否則有安全風險)
        }
        ARouter.init(this); // 盡可能早,推薦在Application中初始化ARouter

        init(this);
        initover(this);
    }

    private boolean isDebug() {
        return BuildConfig.DEBUG;
    }
 }
  • 這樣我們就準備好了ARouter的配置,接下來就可以開始用了,

3.3 ARouter的使用

  1. 在我們即將要跳轉的activity,service或者fragment等等上面宣告添加注解@Route(path = “/xx/xx”),這里path是一個跳轉的路徑,/xx/xx是一個二級目錄,注意:不可以出現路徑相同的情況,不同的組件第一級目錄不可以相同,同一組件的一級目錄可以相同,
  2. 接著在想要跳轉的時候,寫下面這一行代碼就可以跳轉對應路徑的活動ARouter.getInstance().build("/xx/xx").navigation();build里面填的是path地址,后面還可以添加后綴withInteger,withString等等傳遞資料,然后再navigation就可以跳轉了,
  • 這里我們在app層設定兩個跳轉按鈕,在login和share組件中宣告注解,接受跳轉,
login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/login/login1").navigation();
    }
});
share.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/share/share1").navigation();
    }
});
@Route(path = "/login/login1")
public class Login extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

    }
}


@Route(path = "/share/share1")
public class Share extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share);
  }
}

運行結果:
在這里插入圖片描述

  • 點擊跳轉到登陸
    在這里插入圖片描述

  • 點擊跳轉到分享
    在這里插入圖片描述

4. 組件之間的資料傳遞之(介面+實作)

  • 由于主專案與組件,組件與組件之間都是不可以直接使用類的相互參考來進行資料傳遞的,組件間資料互動還有很多其他的方式,比如 EventBus,廣播,資料持久化等方式,但是往往這些方式的互動會不那么直觀,所以對通過 Service 這種形式可以實作的互動,我們最好通過這介面+實作這種方式進行,

4.1 (介面+實作)的大概思路

  • 比如這里我們在share組件中要獲取到登錄的相關資訊:由于所有組件都依賴Baselibs組件,所以在Baselibs組件中創建一個介面和ServiceFactroy,然后在login組件中創建類實作這個介面,然后把這個類上傳到ServiceFactroy,然后在share組件中從ServiceFactroy中獲取這個實作類物件,

4.2 (介面+實作)的實操

  1. 創建介面供login組件實作:
public interface LoginService {
     boolean isLogin();
     String getPassword();
}
  1. 創建ServiceFactroy來管理login組件傳遞上來的介面實作類,并設定get,set方法:
public class ServiceFactory {
    private LoginService loginService;
    private ServiceFactory(){

    }
    public static ServiceFactory getInstance(){
        return Inner.serviceFactory;
    }
    private static class Inner{
        private static ServiceFactory serviceFactory = new ServiceFactory();
    }

    public void setLoginService(LoginService loginService){
        this.loginService = loginService;
    }
    public LoginService getLoginService(){
        if(loginService == null){
            return new EmptyService();
        }else{
            return loginService;
        }
    }
}

這里通過靜態內部類方式實作 ServiceFactory 的單例,由于ServiceFactroy中包含著登錄資訊這種重要的唯一的資訊,所以全域只可以有一個ServiceFactroy物件,所以要實作它的單例創建,

  1. 由于login組件可能并沒傳遞過來一個實作類,share就呼叫get方法,為了防止例外,我們還要創建一個服務的空實作,當login并未上傳實作類時,get回傳這個空實作,
public class EmptyService implements LoginService{
    @Override
    public boolean isLogin() {
        return false;
    }

    @Override
    public String getPassword() {
        return null;
    }
}
  1. 接下來就是在login組件中,實作這個LoginService介面,并在activity中點擊登錄按鈕后,會存盤登陸狀態和用戶資訊,并上傳到這個介面實作類,再將這個介面實作類上傳到ServiceFactroy,這里我們還創建了一個LoginUtil類作為一個傳遞橋梁來存盤這些資料,
  • 首先是實作介面:
public class AccountService implements LoginService {

    private boolean login;
    private String password;

    public AccountService(boolean login, String password) {
        this.login = login;
        this.password = password;
    }

    @Override
    public boolean isLogin() {
        return login;
    }

    @Override
    public String getPassword() {
        return password;
    }
}
  • 存盤資料的工具類:
public class LoginUtil {
    static boolean isLogin = false;
    static String password = null;
}
  • 在activity中點擊登錄按鈕的實作操作:
login = (Button)findViewById(R.id.login_text);
login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LoginUtil.isLogin = true;
        LoginUtil.password = "admin";
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
    }
});
  • 因為我們并不知道什么時候其他的組件就會在ServiceFactroy中獲得這個類,所以我們上傳這個介面實作類要在專案剛剛開始的時候,所以就在login的application中實作上傳,
public class LoginApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
     }
}
  1. 最后一步就是在share組件中獲取登錄資訊了,這里我們在分享組件中添加一個分享按鈕,點擊后,會根據登錄資訊的是否登錄來提示你分享是否成功,
share = (Button)findViewById(R.id.share_text);
share.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(ServiceFactory.getInstance().getLoginService().isLogin()){
            Toast.makeText(Share.this,"分享成功!",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(Share.this,"分享失敗,請先登錄!",Toast.LENGTH_SHORT).show();
        }
    }
});

注意: 在這里出現了一個問題,就是我們想在專案最開始運行的時候,就要在login登陸組件中上傳一個實作類到ServiceFactroy中,并且將這個命令寫到了login組件的application中,但是一個專案只可以有一個application,也就是說Login組件中的application不會初始化,所以我們就涉及到了一個Application的動態配置,下面目錄6中會說如何操作,

5. 組件之間的資料傳遞之(EventBus)

5.1 什么是EventBus?

  • EventBus是一種用于Android的發布/訂閱事件總線,它有很多優點:簡化應用組件間的通信;解耦事件的發送者和接收者;避免復雜和容易出錯的依賴和生命周期的問題;很快,專門為高性能優化過等等,
  • EventBus采取的是訂閱者/發布者的模式,發布者通過EventBus發布事件,訂閱者通過EventBus訂閱事件,當發布者發布事件時,訂閱該事件的訂閱者的事件處理方法將被呼叫,

5.2 使用EventBus的準備作業

  1. 要使用EventBus,第一項準備作業就是添加EventBus的依賴,我們這里直接在Baselibs中添加,
dependencies {
    api 'org.greenrobot:eventbus:3.1.1'
}
  1. 剛才說過,EventBus采用的是訂閱者/發布者模式,也就是說,我們要先建立一個事件,然后再想發送相關資訊的組件創建發布者,然后在要接收資訊的組件創建訂閱者并且獲取資訊,所以第二步就是創建事件,隨便創建一個類,在其中寫出要傳遞的資訊,在加入get/set方法即可,
public class EventMessage {

    String account;

    public EventMessage(String account) {
        this.account = account;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

由于這個事件可能在每個組件都會發布或接收,為了讓其他所有組件都可以獲取到這個類的實體,所以就直接在Baselibs組件中main/java下創建一個檔案EventBus,在其中寫定義的事件類,
在這里插入圖片描述

  • 準備作業就這些就可以了,

5.3 EventBus的基本使用

5.3.1 訂閱事件

  1. 首先我們要在準備訂閱事件的組件中注冊訂閱者,具體就是在activity的onCreate中注冊,在onDestroy中注銷,呼叫EventBus.getDefault().register()和EventBus.getDefault().unregister()方法,
  • 這里我又建立了一個Mine模塊,和分享模塊類似,只不過這里展示用戶的個人資訊,為了區分EventBus和介面+實作的兩種之間的區別,所以我們在Mine模塊中注冊,
@Route(path = "/mine/mine1")
public class Mine extends AppCompatActivity {

    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mine);
        textView = (TextView)findViewById(R.id.message);
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}
  1. 接下來訂閱者需要定義事件處理方法(也稱為訂閱者方法),當發布對應型別的事件時,該方法將被呼叫,EventBus使用 @Subscribe 注解來定義訂閱者方法,方法名可以是任意合法的方法名,引數型別為訂閱事件的型別,
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)
public void showEventMessage(EventMessage message){
    textView.setText(message.getAccount());
}

至于threadMode和sticky我們一會單獨說,

5.3.2 發布事件

  1. 在需要的地方發布事件,所有訂閱了該型別事件并已注冊的訂閱者將在任何時候收到該事件,這里在login組件中運用了EventBus.getDefault().post()方法來發布一個事件,
login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LoginUtil.isLogin = true;
        LoginUtil.password = "admin";
        EventBus.getDefault().post(new EventMessage(LoginUtil.password));           //發送EventBus
    }
});
  • 到這里EventBus的簡單使用就說完了,接下來是Eventbus的幾個進階用法,

5.4 EventBus的進階使用

5.4.1 EventBus的執行緒模式

  • 在剛才創建訂閱方法時,注釋中出現了threadMode,這就是執行緒模式,EventBus支持訂閱者方法在不同于發布事件所在執行緒的執行緒中被呼叫,你可以使用執行緒模式來指定呼叫訂閱者方法的執行緒,EventBus總共支持5種執行緒模式:
  1. ThreadMode.POSTING 訂閱者方法將在發布事件所在的執行緒中被呼叫,這是 默認的執行緒模式,事件的傳遞是同步的,一旦發布事件,所有該模式的訂閱者方法都將被呼叫,這種執行緒模式意味著最少的性能開銷,因為它避免了執行緒的切換,因此,對于不要求是主執行緒并且耗時很短的簡單任務推薦使用該模式,使用該模式的訂閱者方法應該快速回傳,以避免阻塞發布事件的執行緒,這可能是主執行緒,
  2. ThreadMode.MAIN訂閱者方法將在主執行緒(UI執行緒)中被呼叫,因此,可以在該模式的訂閱者方法中直接更新UI界面,如果發布事件的執行緒是主執行緒,那么該模式的訂閱者方法將被直接呼叫,使用該模式的訂閱者方法必須快速回傳,以避免阻塞主執行緒,
  3. ThreadMode.MAIN_ORDERED 訂閱者方法將在主執行緒(UI執行緒)中被呼叫,因此,可以在該模式的訂閱者方法中直接更新UI界面,事件將先進入佇列然后才發送給訂閱者,所以發布事件的呼叫將立即回傳,這使得事件的處理保持嚴格的串行順序,使用該模式的訂閱者方法必須快速回傳,以避免阻塞主執行緒,
  4. ThreadMode.BACKGROUND 訂閱者方法將在后臺執行緒中被呼叫,如果發布事件的執行緒不是主執行緒,那么訂閱者方法將直接在該執行緒中被呼叫,如果發布事件的執行緒是主執行緒,那么將使用一個單獨的后臺執行緒,該執行緒將按順序發送所有的事件,使用該模式的訂閱者方法應該快速回傳,以避免阻塞后臺執行緒,
  5. ThreadMode.ASYNC 訂閱者方法將在一個單獨的執行緒中被呼叫,因此,發布事件的呼叫將立即回傳,如果訂閱者方法的執行需要一些時間,例如網路訪問,那么就應該使用該模式,避免觸發大量的長時間運行的訂閱者方法,以限制并發執行緒的數量,EventBus使用了一個執行緒池來有效地重用已經完成呼叫訂閱者方法的執行緒,

5.4.2 EventBus的黏性事件

  • 在EventBus中,如果先發布了事件,然后有訂閱者訂閱了該事件,那么除非再次發布該事件,否則訂閱者將永遠接收不到該事件,此時,可以使用粘性事件,發布一個粘性事件之后,EventBus將在記憶體中快取該粘性事件,當有訂閱者訂閱了該粘性事件,訂閱者將接收到該事件,
  1. 黏性事件的訂閱方法的注釋上要寫上sticky = true,然后發布黏性事件也有所不同,發布黏性事件用的不是EventBus.getDefault().post()方法,而是EventBus.getDefault().postSticky()方法,
@Subscribe(threadMode = ThreadMode.POSTING,sticky = true)		//訂閱
public void showEventMessage(EventMessage message){
}

EventBus.getDefault().postSticky(new EventMessage(LoginUtil.password));           //發送黏性EventBus
  • 正好我們這個專案要符合最開始就獲取到登錄資訊,所以要發布一個黏性事件才合適,

6.Applicaion的動態配置

  • 在上面我們說的組件之間資訊相互傳遞的問題中,采用介面+實作的方法傳遞時,我們想讓專案初始化時,在Login組件中發送出介面實作類,但是在一個專案中只有一個application,Login組件的application是不會初始化的,所以這時候login組件就不會發送介面實作類,
  • 解決方法:將組件的類強參考到主 Module 的 Application 中進行初始化,這就必須要求主模塊可以直接訪問組件中的類,而我們又不想在開發程序中主模塊能訪問組件中的類,這里可以通過反射來實作組件 Application 的初始化,也就是Application的動態配置,
  • ==思路:==建立一個BaseApplication繼承Application,然后令其他的application類都繼承自BaseApplication,在BaseApplication中寫兩個方法,一個是初始化,一個是初始化之后呼叫的函式,然后在app主模塊里面利用反射,得到Login中的application類,然后呼叫其初始化方法即可,

6.1 建立一個BaseApplication

  • 由于Bselibs組件是其他組件都依賴的,并且要求繼承BaseApplication的類必須重寫方法,所以我們在Baselibs中創建抽象類BaseApplication繼承Application,如下:
public abstract class BaseApplication extends Application {
    public abstract void init(Application application);         //初始當前組件呼叫的方法
    public abstract void initover(Application application);               //其他需要呼叫的方法
}

6.2 修改主Module的application類

  • 直接上代碼
public class MainApplication extends BaseApplication {

    @Override
    public void onCreate() {
        super.onCreate();
        //ARouter后臺有ILogger介面,定義了一些輸出日志
        if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init程序中將無效
            ARouter.openLog();     // 列印日志
            ARouter.openDebug();   // 開啟除錯模式(如果在InstantRun模式下運行,必須開啟除錯模式!線上版本需要關閉,否則有安全風險)
        }
        ARouter.init(this); // 盡可能早,推薦在Application中初始化ARouter

        init(this);
        initover(this);
    }

    private boolean isDebug() {
        return BuildConfig.DEBUG;
    }


    @Override
    public void init(Application application) {...}

    @Override
    public void initover(Application application) {...}
}

6.3 修改Login組件的application類

  • 直接代碼
public class LoginApplication extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        init(this);
        initover(this);
    }


    @Override
    public void init(Application application) {
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
    }

    @Override
    public void initover(Application application) {

    }
}
  • 在這里我們完成了application的配置,接下來我們就可以用反射的方式在app主Module的application類中獲取Login組件的application類,執行其方法,

6.4 運用反射獲取其他組件的application進行初始化

  • 在app模塊中的application中重寫的兩個方法,獲取并進行初始化,
@Override
public void init(Application application) {
    for(String moduleApp : AppConfig.moduleApps){
        try{
            Class clazz = Class.forName(moduleApp);
            BaseApplication baseApplication = null;
            baseApplication = (BaseApplication) clazz.newInstance();
            baseApplication.init(this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

@Override
public void initover(Application application) {
    for(String moduleApp : AppConfig.moduleApps){
        try{
            Class clazz = Class.forName(moduleApp);
            BaseApplication baseApplication = null;
            baseApplication = (BaseApplication) clazz.newInstance();
            baseApplication.initover(this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • 這樣操作之后,我們在專案開始的時候,Login組件就會發送出一個介面實作類到ServiceFactroy了,

7.主專案使用各個組件的類的方法

  • 這里介紹三個方法,一個是反射,一個是通過介面+實作的方法,另外一個就是路由的方法,

7.1 反射的方法

  • 和剛剛說的獲取Login組件的application類是一樣的,只不過這次我們利用反射獲取碎片實體,將碎片加載到activity中,然后activity再將它放到合適的位置,

7.2 介面+實作的方法

  • 和之前的也一樣,在ServiceFactroy中在創建一個碎片物件,然后添加一個建構式,引數是碎片,在提供一個get和set方法就可以了,
  • 不要忘記,在Baselibs中在添加一個空實作,以免get方法呼叫時出現錯誤,

7.3 路由的實作方法

  • 簡單明了一行代碼,
mineFragment = (Fragment) ARouter.getInstance().build("/mine/fragment").navigation();
  • 就這樣輕輕松松的獲取到了組件碎片的實體,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/211945.html

標籤:其他

上一篇:Android WebView

下一篇:行程間的實時通訊方案: local socket(解決擴展和容器應用的實時通訊問題)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more