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

安卓一步一步搭建組件化

2020-11-12 08:13:40 後端開發

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

  • 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/houduan/211654.html

標籤:java

上一篇:flutter doctor 檢測不到AndroidStudio4.1已安裝的Dart、Flutter插件

下一篇:帶勁!3個常見的自動化軟體測驗面試題深度剖析!

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more