<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
一個大型APP版本一定會不斷的迭代,APP裡的功能也會隨之增加,專案的業務也會變的越來越複雜,這樣導致專案程式碼也變的越來越多,開發效率也會隨之下降。並且單一工程下程式碼耦合嚴重,每修改一處程式碼後都要重新編譯,非常耗時,單獨修改的一個模組無法單獨測試。
元件化架構的目的是讓各個業務變得相對獨立,各個元件在元件模式下可以獨立開發偵錯,整合模式下又可以整合到“app殼工程”中,從而得到一個具有完整功能的APP。
元件化每一個元件都可以是一個APP可以單獨修改偵錯,而不影響總專案。
編譯速度: 可以但需測試單一模組,極大提高了開發速度
超級解耦: 極度降低了模組間的耦合,便於後期的維護和更新
功能重用: 某一塊的功能在另外的元件化專案中使用只需要單獨依賴這一模組即可
便於團隊開發: 元件化架構是團隊開發必然會選擇的一種開發方式,它能有效的使團隊更好的共同作業
這裡以演示為例,只設定登入這一個功能元件
元件化開發要注意的幾點問題 :
Gradle
中的版本號的統一管理AppIication
和Library
之間如何做到隨意切換AndroidManifest. xml
檔案的區分Library
不能在Gradle
檔案中有applicationId
這裡以演示為例,只設定登入和個人中心這兩個功能元件
並且在module裡新建一個activity
到這裡我們看到login
和我們的app
都在有一個綠點證明建立成功
個人中心member
模組建立同理,並且每個模組目前都可以獨立執行。
每一個模組都是一個application
,所以每個模組都會有一個build.gradle
,各個模組裡面的設定不同,我們需要重新統一Gradle
在主模組建立config.gradle
在config.gradle
裡去新增一些版本號
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() }
在主模組的build.gradle
裡新增
apply from: "config.gradle"
在各模組中去參照這些版本號
參照格式如下,兩種寫法均可
compileSdkVersion rootProject.ext.android["compileSdkVersion"] buildToolsVersion rootProject.ext.android.buildToolsVersion
參照前:
參照後:
並且使用同樣的方法,我們還可以統一我們的依賴庫在config.gradle
裡去新增我們要依賴的庫,並在各個模組中去新增依賴
implementation rootProject.ext.dependencies.publicImplementation
也可以採用第二種寫法
dependencies = [ "appcompat" : 'androidx.appcompat:appcompat:1.2.0', "material" : 'com.google.android.material:material:1.2.1', "constraintLayout" : 'androidx.constraintlayout:constraintlayout:2.0.4',//約束性佈局 //test "junit" : "junit:junit:4.13.1", "testExtJunit" : 'androidx.test.ext:junit:1.1.2',//測試依賴,新建專案時會預設新增,一般不建議新增 "espressoCore" : 'androidx.test.espresso:espresso-core:3.3.0',//測試依賴,新建專案時會預設新增,一般不建議新增 ]
新增依賴:
dependencies { implementation rootProject.ext.dependencies.appcompat implementation rootProject.ext.dependencies["constraintLayout"] testImplementation rootProject.ext.dependencies["junit"] androidTestImplementation rootProject.ext.dependencies["testExtJunit"] androidTestImplementation rootProject.ext.dependencies["espressoCore"] }
和新建module
一樣,這裡需要新建一個library
我們把它命名為Baselibs
同樣需要統一版本號,由於這是一個library
模組,所以它不需要applicationId
我們一樣可以把它寫進config.gradle
other:[path:':Baselibs']
在每個模組去呼叫
implementation project(rootProject.ext.dependencies.other)
同理,當本地庫為單獨所用,我們可以直接呼叫,而不需要將其寫入config.gradle
,兩種方法選擇合適使用即可。
implementation project(':Baselibs')
但有時因為gradle
版本問題,我們可能無法依賴到這些公共庫,因為我們在config.gradle
裡是以陣列形式定義的,這時我們可以同for-each迴圈的方法將其依次匯入config.gradle
裡
dependencies = [ ...... other:[':Baselibs'] ]
其他模組的build.gradle
dependencies { ...... rootProject.ext.dependencies.other.each{ implementation project(it) }
在主模組gradle.properties
裡新增布林型別選項。
在各個模組的build.gradle
裡新增更改語句
if(is_Module.toBoolean()){ apply plugin: 'com.android.application' }else{ apply plugin: 'com.android.library' }
每個模組的applicationId
也需要處理
if(is_Module.toBoolean()){ applicationId "activitytest.com.example.login" }
當我們將is_module
改為false時,再次執行編譯器我們的模組都不能單獨執行了
在app模組中新增判斷依賴就可以在整合模式下將各模組新增到app主模組中
// 每加入一個新的模組,就需要在下面對應的新增一行 if (is_Module.toBoolean())]) { implementation project(path:':login') implementation project(path:':member') }
為了單獨開發載入不同的AndroidManifest
這裡需要重新區分下。
在元件模組裡的main
檔案裡新建manifest
資料夾
並且重寫一個AndroidManifest.xml
檔案,整合模式下,業務元件的表單是絕對不能擁有自己的 Application 和 launch 的 Activity的,也不能宣告APP名稱、圖示等屬性,總之app殼工程有的屬性,業務元件都不能有,在這個表單中只宣告了應用的主題,而且這個主題還是跟app殼工程中的主題是一致的
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.login"> <application android:theme="@style/Theme.MoudleTest"> <activity android:name=".LoginActivity"> </activity> </application> </manifest>
並且我們還要使其在不同的模式下載入不同的AndroidManifest
只需在各模組的build.gradle
裡新增更改語句
sourceSets { main { if (is_Module.toBoolean()) { manifest.srcFile 'src/main/AndroidManifest.xml' } else { manifest.srcFile 'src/main/mainfest/AndroidManifest.xml' } } }
每個模組在執行時都會有自己的application,而在元件化開發過程中,我們的主模組只能有一個application,但在單獨執行時又需要自己的application這裡就需要設定一下。
在業務模組新增新資料夾命名module
在裡面建一個application檔案
並且我們在build.gradle
檔案裡設定module
資料夾使其在單獨執行時能夠執行單獨的application
在設定manifest的語句中新增java.srcDir 'src/main/module'
sourceSets { main { if (is_Module.toBoolean()) { manifest.srcFile 'src/main/AndroidManifest.xml' java.srcDir 'src/main/module' } else { manifest.srcFile 'src/main/manifest/AndroidManifest.xml' } } }
同時我們在basic
基礎層內新建application,用於載入一些資料的初始化
public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); Log.e("fff","baseapplication"); } }
在業務模組內module
裡重寫該模組的application
public class LoginApplication extends BaseApplication { @Override public void onCreate() { super.onCreate(); } }
至此,元件化框架搭建結束
這裡採用阿里巴巴的開源庫ARouter
來實現跳轉功能,我會在以後的文章單獨拿出一篇來一步步去解讀Arouter
原始碼,讓我們自己去搭建一個自己的路由
一個用於幫助 Android App 進行元件化改造的框架 —— 支援模組間的路由、通訊、解耦
由 github 上 ARouter 的介紹可以知道,它可以實現元件間的路由功能。路由是指從一個介面上收到封包,根據資料路由包的目的地址進行定向並轉發到另一個介面的過程。這裡可以體現出路由跳轉的特點,非常適合元件化解耦。
要使用 ARouter 進行介面跳轉,需要我們的元件對 Arouter 新增依賴,因為所有的元件都依賴了 Baselibs
模組,所以我們在 Baselibs
模組中新增 ARouter 的依賴即可。其它元件共同依賴的庫也最好都放到 Baselibs
中統一依賴。
這裡需要注意的是,arouter-compiler
的依賴需要所有使用到 ARouter 的模組和元件中都單獨新增,不然無法在 apt 中生成索引檔案,也就無法跳轉成功。並且在每一個使用到 ARouter 的模組和元件的 build.gradle 檔案中,其 android{} 中的 javaCompileOptions 中也需要新增特定設定。
在Baselibs
裡的build.gradle
新增依賴
dependencies { api 'com.alibaba:arouter-api:1.3.1' // arouter-compiler 的註解依賴需要所有使用 ARouter 的 module 都新增依賴 annotationProcessor 'com.alibaba:arouter-compiler:1.1.4' } // 所有使用到 ARouter 的元件和模組的 build.gradle android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ moduleName : project.getName() ] } } } } dependencies { ... implementation project (':base') annotationProcessor 'com.alibaba:arouter-compiler:1.1.4' }
主模組需要對跳轉模組進行依賴:
// 主專案的 build.gradle 需要新增對 login 元件和 share 元件的依賴 dependencies { // ... 其他 implementation project(':login') implementation project(':share') }
新增了對 ARouter 的依賴後,還需要在專案的 Application 中將 ARouter 初始化,我們這裡將 ARouter 的初始化工作放到主模組Application 的 onCreate()
方法中,在應用啟動的同時將 ARouter 初始化。
public class MainApplication extends Application { @Override public void onCreate() { super.onCreate(); // 初始化 ARouter if (isDebug()) { // 這兩行必須寫在init之前,否則這些設定在init過程中將無效 // 列印紀錄檔 ARouter.openLog(); // 開啟偵錯模式(如果在InstantRun模式下執行,必須開啟偵錯模式!線上版本需要關閉,否則有安全風險) ARouter.openDebug(); } // 初始化 ARouter ARouter.init(this); } private boolean isDebug() { return BuildConfig.DEBUG; } }
這裡我們在首頁新增登入
和分享
兩個跳轉頁面。
login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ARouter.getInstance().build("/login/login").navigation(); } });
share.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ARouter.getInstance().build("/share/share").navigation(); } });
然後,需要在登入和分享元件中分別新增 LoginActivity
和 ShareActivity
,然後分別為兩個 Activity 新增註解 Route,其中path
是跳轉的路徑,這裡的路徑需要注意的是至少需要有兩級,/xx/xx
@Route(path = "/login/login") public class Login extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); } }
@Route(path = "/share/share") public class Share extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share); } }
這樣就可以實現跳轉了。
由於主專案與元件,元件與元件之間都是不可以直接使用類的相互參照來進行資料傳遞的,那麼在開發過程中如果有元件間的資料傳遞時應該如何解決呢,這裡我們可以採用 [介面 + 實現] 的方式來解決。
在Baselibs
基礎庫裡定義元件可以對外提供存取自身資料的抽象方法的 Service。並且提供了一個 ServiceFactory
,每個元件中都要提供一個類實現自己對應的 Service 中的抽象方法。在元件載入後,需要建立一個實現類的物件,然後將實現了 Service 的類的物件新增到ServiceFactory
中。這樣在不同元件互動時就可以通過 ServiceFactory
獲取想要呼叫的元件的介面實現,然後呼叫其中的特定方法就可以實現元件間的資料傳遞與方法呼叫。
當然,ServiceFactory
中也會提供所有的 Service 的空實現,在元件單獨偵錯或部分整合偵錯時避免出現由於實現類物件為空引起的空指標異常。
下面我們就按照這個方法來解決元件間資料傳遞與方法的相互呼叫這個問題,這裡我們通過分享元件 中呼叫 登入元件 中的方法來獲取登入狀態是否登入這個場景來演示。
其中 service資料夾中定義介面,LoginService
介面中定義了 Login 元件向外提供的資料傳遞的介面方法,EmptyService
中是 service 中定義的介面的空實現,ServiceFactory
接收元件中實現的介面物件的註冊以及向外提供特定元件的介面實現。
LoginService
public interface LoginService { /** * 是否已經登入 * @return */ boolean isLogin(); /** * 獲取登入使用者的 Password * @return */ String getPassword(); }
EmptyService
public class EmptyService implements LoginService { @Override public boolean isLogin() { return false; } @Override public String getPassword() { return null; } }
ServiceFactory
public class ServiceFactory { private LoginService loginService; private ServiceFactory(){ /** * 禁止外部建立 ServiceFactory 物件 */ private ServiceFactory() { } /** * 通過靜態內部類方式實現 ServiceFactory 的單例 */ public static ServiceFactory getInstance() { return Inner.serviceFactory; } private static class Inner { private static ServiceFactory serviceFactory = new ServiceFactory(); } /** * 接收 Login 元件實現的 Service 範例 */ public void setLoginService(LoginService loginService){ this.loginService = loginService; } /** * 返回 Login 元件的 Service 範例 */ public LoginService getLoginService(){ if(loginService == null){ return new EmptyService(); }else{ return loginService; } } }
在login模組
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; } }
這裡新建一個Util類用來儲存登入資料
public class LoginUtil { static boolean isLogin = false; static String password = null; }
實現一下登入操作
login = (Button)findViewById(R.id.login); 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)); } });
在login模組的application裡定義ServiceFactory類
public class LoginApplication extends Application { @Override public void onCreate() { super.onCreate(); ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password)); } }
在分享模組獲取登入資訊
share = (Button)findViewById(R.id.share); share.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(ServiceFactory.getInstance().getLoginService().isLogin()){ Toast.makeText(ShareActivity.this,"分享成功!",Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(ShareActivity.this,"分享失敗,請先登入!",Toast.LENGTH_SHORT).show(); } } });
一個專案時只能有一個 Application 的,Login 作為元件時,主模組的 Application 類會初始化,而 Login 元件中的 Applicaiton 不會初始化。確實是存在這個問題的,我們這裡先將 Service 的註冊放到其活動裡,稍後我們會解決 Login 作為元件時 Appliaciton 不會初始化的問題。
在主模組中有 Application 等情況下,元件在集中偵錯時其 Applicaiton 不會初始化的問題。而我們元件的 Service 在 ServiceFactory 的註冊又必須放到元件初始化的地方。
為了解決這個問題可以將元件的 Service 類強參照到主 Module 的 Application 中進行初始化,這就必須要求主模組可以直接存取元件中的類。而我們又不想在開發過程中主模組能存取元件中的類,這裡可以通過反射來實現元件 Application 的初始化。
在Baselibs
基礎庫模組
public abstract class BaseApplication extends Application { /** * Application 初始化 */ public abstract void initModuleApp(Application application); /** * 所有 Application 初始化後的自定義操作 */ public abstract void initModuleData(Application application); //其他需要呼叫的方法 }
這裡我們以Login模組為例
public class LoginApplication extends BaseApplication{ @Override public void onCreate() { super.onCreate(); initModuleApp(this); initModuleData(this); } @Override public void initModuleApp(Application application) { ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password)); } @Override public void initModuleData(Application application) { } }
在Baselibs
模組定義一個靜態的 String 陣列,我們將需要初始化的元件的 Application 的完整類名放入到這個陣列中。
public class AppConfig { private static final String LoginApp = "com.example.login.LoginApplication"; public static String[] moduleApps = { LoginApp }; }
// 主 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(); } } } }
到這裡我們就通過反射,完成了元件 Application 的初始化操作,也實現了元件與化中的解耦需求。
我們在開發過程中經常使用 Fragment。一般情況下,我們都是直接通過存取具體 Fragment 類的方式實現 Fragment 的範例化,但是現在為了實現模組與元件間的解耦,在移除元件時不會由於參照的 Fragment 不存在而編譯失敗,我們就不能模組中直接存取元件的 Fragment 類。
這裡介紹兩種方法
這裡可以採用ARouter直接呼叫
fragment = (Fragment) ARouter.getInstance().build("/login/fragment").navigation();
我們還是以Login
模組為例,假如在該模組建立一個使用者介面,命名為UserFragment
首先,在 Login
元件中建立 UserFragment
,然後在 LoginService
介面中新增newUserFragment
方法返回一個Fragment
,在Login
元件中的 AccountService
和 Baselibs
中 LoginService
的空實現類中實現這個方法,然後在主模組中通過 ServiceFactory
獲取 LoginService
的實現類物件,呼叫其 newUserFragment
即可獲取到 UserFragment
的範例。
// Baselibs 模組的 LoginService public interface LoginService { //其他程式碼... Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag); }
// Login 元件中的 AccountService public class AccountService implements LoginService { // 其他程式碼 ... @Override public Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag) { FragmentTransaction transaction = manager.beginTransaction(); // 建立 UserFragment 範例,並新增到 Activity 中 Fragment userFragment = new UserFragment(); transaction.add(containerId, userFragment, tag); transaction.commit(); return userFragment; } }
// 主模組的 FragmentActivity public class FragmentActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); // 通過元件提供的 Service 實現 Fragment 的範例化 ServiceFactory.getInstance().getAccountService().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, ""); } }
到此這篇關於Android元件化原理詳細介紹的文章就介紹到這了,更多相關Android元件化 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45