Android組件化原理詳細介紹
什么是組件化?
一個大型APP版本一定會不斷的迭代,APP里的功能也會隨之增加,項目的業(yè)務也會變的越來越復雜,這樣導致項目代碼也變的越來越多,開發(fā)效率也會隨之下降。并且單一工程下代碼耦合嚴重,每修改一處代碼后都要重新編譯,非常耗時,單獨修改的一個模塊無法單獨測試。
組件化架構(gòu)的目的是讓各個業(yè)務變得相對獨立,各個組件在組件模式下可以獨立開發(fā)調(diào)試,集成模式下又可以集成到“app殼工程”中,從而得到一個具有完整功能的APP。
組件化每一個組件都可以是一個APP可以單獨修改調(diào)試,而不影響總項目。

為什么使用組件化?
編譯速度: 可以但需測試單一模塊,極大提高了開發(fā)速度
超級解耦: 極度降低了模塊間的耦合,便于后期的維護和更新
功能重用: 某一塊的功能在另外的組件化項目中使用只需要單獨依賴這一模塊即可
便于團隊開發(fā): 組件化架構(gòu)是團隊開發(fā)必然會選擇的一種開發(fā)方式,它能有效的使團隊更好的協(xié)作
一步步搭建組件化
這里以演示為例,只設置登錄這一個功能組件
組件化開發(fā)要注意的幾點問題 :
- 要注意包名和資源文件命名沖突問題
Gradle中的版本號的統(tǒng)一管理- 組件在
AppIication和Library之間如何做到隨意切換 AndroidManifest. xml文件的區(qū)分Library不能在Gradle文件中有applicationId
這里以演示為例,只設置登錄和個人中心這兩個功能組件
1.新建模塊

并且在module里新建一個activity

到這里我們看到login和我們的app都在有一個綠點證明創(chuàng)建成功

個人中心member模塊創(chuàng)建同理,并且每個模塊目前都可以獨立運行。

2.統(tǒng)一Gradle版本號
每一個模塊都是一個application,所以每個模塊都會有一個build.gradle,各個模塊里面的配置不同,我們需要重新統(tǒng)一Gradle
在主模塊創(chuàng)建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
引用前:

引用后:

并且使用同樣的方法,我們還可以統(tǒng)一我們的依賴庫在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"]
}3.創(chuàng)建基礎庫
和新建module一樣,這里需要新建一個library我們把它命名為Baselibs

同樣需要統(tǒng)一版本號,由于這是一個library模塊,所以它不需要applicationId

我們一樣可以把它寫進config.gradle
other:[path:':Baselibs']
在每個模塊去調(diào)用
implementation project(rootProject.ext.dependencies.other)
同理,當本地庫為單獨所用,我們可以直接調(diào)用,而不需要將其寫入config.gradle,兩種方法選擇合適使用即可。
implementation project(':Baselibs')但有時因為gradle版本問題,我們可能無法依賴到這些公共庫,因為我們在config.gradle里是以數(shù)組形式定義的,這時我們可以同for-each循環(huán)的方法將其依次導入config.gradle里
dependencies = [
......
other:[':Baselibs']
]其他模塊的build.gradle
dependencies {
......
rootProject.ext.dependencies.other.each{
implementation project(it)
}4.組件模式和集成模式轉(zhuǎn)換
在主模塊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')
}5.AndroidManifest的切換
為了單獨開發(fā)加載不同的AndroidManifest這里需要重新區(qū)分下。
在組件模塊里的main文件里新建manifest文件夾

并且重寫一個AndroidManifest.xml文件,集成模式下,業(yè)務組件的表單是絕對不能擁有自己的 Application 和 launch 的 Activity的,也不能聲明APP名稱、圖標等屬性,總之a(chǎn)pp殼工程有的屬性,業(yè)務組件都不能有,在這個表單中只聲明了應用的主題,而且這個主題還是跟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'
}
}
}6.*業(yè)務Application切換
每個模塊在運行時都會有自己的application,而在組件化開發(fā)過程中,我們的主模塊只能有一個application,但在單獨運行時又需要自己的application這里就需要配置一下。
在業(yè)務模塊添加新文件夾命名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基礎層內(nèi)新建application,用于加載一些數(shù)據(jù)的初始化
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.e("fff","baseapplication");
}
}在業(yè)務模塊內(nèi)module里重寫該模塊的application
public class LoginApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
}
}至此,組件化框架搭建結(jié)束
組件之間的跳轉(zhuǎn)
這里采用阿里巴巴的開源庫ARouter來實現(xiàn)跳轉(zhuǎn)功能,我會在以后的文章單獨拿出一篇來一步步去解讀Arouter源碼,讓我們自己去搭建一個自己的路由
一個用于幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦
由 github 上 ARouter 的介紹可以知道,它可以實現(xiàn)組件間的路由功能。路由是指從一個接口上收到數(shù)據(jù)包,根據(jù)數(shù)據(jù)路由包的目的地址進行定向并轉(zhuǎn)發(fā)到另一個接口的過程。這里可以體現(xiàn)出路由跳轉(zhuǎn)的特點,非常適合組件化解耦。
要使用 ARouter 進行界面跳轉(zhuǎn),需要我們的組件對 Arouter 添加依賴,因為所有的組件都依賴了 Baselibs模塊,所以我們在 Baselibs 模塊中添加 ARouter 的依賴即可。其它組件共同依賴的庫也最好都放到 Baselibs中統(tǒng)一依賴。
這里需要注意的是,arouter-compiler 的依賴需要所有使用到 ARouter 的模塊和組件中都單獨添加,不然無法在 apt 中生成索引文件,也就無法跳轉(zhuǎn)成功。并且在每一個使用到 ARouter 的模塊和組件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。
1.添加依賴
在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'
}
主模塊需要對跳轉(zhuǎn)模塊進行依賴:
// 主項目的 build.gradle 需要添加對 login 組件和 share 組件的依賴
dependencies {
// ... 其他
implementation project(':login')
implementation project(':share')
}2.初始化ARouter
添加了對 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();
// 開啟調(diào)試模式(如果在InstantRun模式下運行,必須開啟調(diào)試模式!線上版本需要關(guān)閉,否則有安全風險)
ARouter.openDebug();
}
// 初始化 ARouter
ARouter.init(this);
}
private boolean isDebug() {
return BuildConfig.DEBUG;
}
}3.添加跳轉(zhuǎn)
這里我們在首頁添加登錄和分享兩個跳轉(zhuǎn)頁面。
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 是跳轉(zhuǎn)的路徑,這里的路徑需要注意的是至少需要有兩級,/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);
}
}這樣就可以實現(xiàn)跳轉(zhuǎn)了。
組件之間的數(shù)據(jù)傳遞
由于主項目與組件,組件與組件之間都是不可以直接使用類的相互引用來進行數(shù)據(jù)傳遞的,那么在開發(fā)過程中如果有組件間的數(shù)據(jù)傳遞時應該如何解決呢,這里我們可以采用 [接口 + 實現(xiàn)] 的方式來解決。
在Baselibs基礎庫里定義組件可以對外提供訪問自身數(shù)據(jù)的抽象方法的 Service。并且提供了一個 ServiceFactory,每個組件中都要提供一個類實現(xiàn)自己對應的 Service 中的抽象方法。在組件加載后,需要創(chuàng)建一個實現(xiàn)類的對象,然后將實現(xiàn)了 Service 的類的對象添加到ServiceFactory 中。這樣在不同組件交互時就可以通過 ServiceFactory 獲取想要調(diào)用的組件的接口實現(xiàn),然后調(diào)用其中的特定方法就可以實現(xiàn)組件間的數(shù)據(jù)傳遞與方法調(diào)用。
當然,ServiceFactory 中也會提供所有的 Service 的空實現(xiàn),在組件單獨調(diào)試或部分集成調(diào)試時避免出現(xiàn)由于實現(xiàn)類對象為空引起的空指針異常。
下面我們就按照這個方法來解決組件間數(shù)據(jù)傳遞與方法的相互調(diào)用這個問題,這里我們通過分享組件 中調(diào)用 登錄組件 中的方法來獲取登錄狀態(tài)是否登錄這個場景來演示。
1.定義接口
其中 service文件夾中定義接口,LoginService 接口中定義了 Login 組件向外提供的數(shù)據(jù)傳遞的接口方法,EmptyService 中是 service 中定義的接口的空實現(xiàn),ServiceFactory 接收組件中實現(xiàn)的接口對象的注冊以及向外提供特定組件的接口實現(xiàn)。

LoginService
public interface LoginService {
/**
* 是否已經(jīng)登錄
* @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(){
/**
* 禁止外部創(chuàng)建 ServiceFactory 對象
*/
private ServiceFactory() {
}
/**
* 通過靜態(tài)內(nèi)部類方式實現(xiàn) ServiceFactory 的單例
*/
public static ServiceFactory getInstance() {
return Inner.serviceFactory;
}
private static class Inner {
private static ServiceFactory serviceFactory = new ServiceFactory();
}
/**
* 接收 Login 組件實現(xiàn)的 Service 實例
*/
public void setLoginService(LoginService loginService){
this.loginService = loginService;
}
/**
* 返回 Login 組件的 Service 實例
*/
public LoginService getLoginService(){
if(loginService == null){
return new EmptyService();
}else{
return loginService;
}
}
}2.實現(xiàn)接口
在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類用來存儲登錄數(shù)據(jù)
public class LoginUtil {
static boolean isLogin = false;
static String password = null;
}實現(xiàn)一下登錄操作
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的動態(tài)切換
在主模塊中有 Application 等情況下,組件在集中調(diào)試時其 Applicaiton 不會初始化的問題。而我們組件的 Service 在 ServiceFactory 的注冊又必須放到組件初始化的地方。
為了解決這個問題可以將組件的 Service 類強引用到主 Module 的 Application 中進行初始化,這就必須要求主模塊可以直接訪問組件中的類。而我們又不想在開發(fā)過程中主模塊能訪問組件中的類,這里可以通過反射來實現(xiàn)組件 Application 的初始化。
1.定義抽象類 BaseApplication 繼承 Application
在Baselibs基礎庫模塊
public abstract class BaseApplication extends Application {
/**
* Application 初始化
*/
public abstract void initModuleApp(Application application);
/**
* 所有 Application 初始化后的自定義操作
*/
public abstract void initModuleData(Application application); //其他需要調(diào)用的方法
}2.所有的組件的 Application 都繼承 BaseApplication
這里我們以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) {
}
}3.定義 AppConfig 類
在Baselibs模塊定義一個靜態(tài)的 String 數(shù)組,我們將需要初始化的組件的 Application 的完整類名放入到這個數(shù)組中。
public class AppConfig {
private static final String LoginApp = "com.example.login.LoginApplication";
public static String[] moduleApps = {
LoginApp
};
}4.主模塊application實現(xiàn)兩個初始化方法
// 主 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 的初始化操作,也實現(xiàn)了組件與化中的解耦需求。
主模塊使用其他組件的 Fragment
我們在開發(fā)過程中經(jīng)常使用 Fragment。一般情況下,我們都是直接通過訪問具體 Fragment 類的方式實現(xiàn) Fragment 的實例化,但是現(xiàn)在為了實現(xiàn)模塊與組件間的解耦,在移除組件時不會由于引用的 Fragment 不存在而編譯失敗,我們就不能模塊中直接訪問組件的 Fragment 類。
這里介紹兩種方法
1.ARouter
這里可以采用ARouter直接調(diào)用
fragment = (Fragment) ARouter.getInstance().build("/login/fragment").navigation();2.反射
我們還是以Login模塊為例,假如在該模塊創(chuàng)建一個用戶界面,命名為UserFragment
首先,在 Login組件中創(chuàng)建 UserFragment,然后在 LoginService 接口中添加newUserFragment方法返回一個Fragment,在Login組件中的 AccountService 和 Baselibs 中 LoginService 的空實現(xiàn)類中實現(xiàn)這個方法,然后在主模塊中通過 ServiceFactory 獲取 LoginService 的實現(xiàn)類對象,調(diào)用其 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();
// 創(chuàng)建 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 實現(xiàn) Fragment 的實例化
ServiceFactory.getInstance().getAccountService().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, "");
}
}到此這篇關(guān)于Android組件化原理詳細介紹的文章就介紹到這了,更多相關(guān)Android組件化 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android全局監(jiān)控click事件的四種方式(小結(jié))
本篇文章主要介紹了android全局監(jiān)控click事件的四種方式(小結(jié)),詳細介紹如何在全局上去監(jiān)聽 click 點擊事件,并做些通用處理或是攔截,有興趣的可以了解一下2017-08-08
Android基于虹軟(ArcSoft)實現(xiàn)人臉識別
人工智能時代快速來臨,其中人臉識別是當前比較熱門的技術(shù),在國內(nèi)也越來越多的運用,例如刷臉打卡,刷臉APP,身份識別,人臉門禁等。本文將為大家介紹Android基于虹軟(ArcSoft)實現(xiàn)人臉識別的demo,快來跟隨小編一起學習吧2021-12-12
Android RecyclerView 實現(xiàn)快速滾動的示例代碼
本篇文章主要介紹了Android RecyclerView 實現(xiàn)快速滾動的示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-09-09
Android中View.post和Handler.post的關(guān)系
這篇文章主要介紹了Android中View.post和Handler.post的關(guān)系,View.post和Handler.post是Android開發(fā)中經(jīng)常使用到的兩個”post“方法,關(guān)于兩者存在的區(qū)別與聯(lián)系,文章詳細分析需要的小伙伴可以參考一下2022-06-06

