Android架構(gòu)師之路-組件化入門

組件化與模塊化

組件

組件指的是單一的功能組件绍填,如視頻組件(VideoSDK)歌逢、支付組件(PaySDK)签杈、路由組件(Router)等,每個組件都能單獨(dú)抽出來制作成SDK

模塊

模塊指的是獨(dú)立的業(yè)務(wù)模塊沽一,如直播模塊(LiveModule)盖溺、首頁模塊(HomeModule)、即時通信模塊(IMModule)等锯玛。模塊相對于組件來說粒度更大,模塊可能包含多種不用的組件。

組件化開發(fā)的好處

(1)避免重復(fù)造輪子攘残,可以節(jié)省開發(fā)和維護(hù)的成本拙友。
(2)可以通過組件和模塊為業(yè)務(wù)基準(zhǔn)合理地安排人力,提高開發(fā)效率歼郭。
(3)不同的項(xiàng)目可以共用一個組件或模塊遗契,確保整體技術(shù)方案的統(tǒng)一性。
(4)為未來插件化共同用一套底層模型做準(zhǔn)備

模塊化開發(fā)的好處

(1)業(yè)務(wù)模塊解耦病曾,業(yè)務(wù)移植更加簡單牍蜂。
(2)多團(tuán)隊(duì)根據(jù)業(yè)務(wù)內(nèi)容進(jìn)行并行開發(fā)和測試。
(3)單個業(yè)務(wù)可以單獨(dú)編譯打包泰涂,加快編譯速度鲫竞。
(4)多個App共用模塊,降低研發(fā)和維護(hù)成本逼蒙。

多Module劃分業(yè)務(wù)和基礎(chǔ)功能从绘,這個概念將作為組件化的基礎(chǔ)。
組件化和模塊化的本質(zhì)思想是一樣的是牢,都是為了代碼重用和業(yè)務(wù)解耦僵井,區(qū)別在于模塊化是業(yè)務(wù)導(dǎo)向,組件化是功能導(dǎo)向驳棱。組件化和模塊化的劃分將更好地為項(xiàng)目插件化開路批什。插件化的模塊發(fā)布和正常發(fā)布有著非常大的差異,已經(jīng)脫離了組件化和模塊化的構(gòu)建機(jī)制社搅,插件化擁有更高效的業(yè)務(wù)解耦驻债。

項(xiàng)目搭建

1.首先我們新建一個項(xiàng)目然后新建一個library(componentlib)和兩個組件(logincomponent、minecomponent)罚渐,如下圖:


在這里插入圖片描述

(1)app:應(yīng)用層用于統(tǒng)籌全部組件却汉,并輸出生成App
(2)logincomponent和minecomponent作為組件來使用,包含一些簡單的業(yè)務(wù)荷并、比如登錄合砂、我的。
(3)componentlib:包含一些基礎(chǔ)庫和對基礎(chǔ)庫的封裝源织,包含圖片加載翩伪、網(wǎng)絡(luò)加載,數(shù)據(jù)存儲(正常是再抽離一層出來谈息,作為Base層)缘屹;還有作為通訊層,來進(jìn)行各個組件的數(shù)據(jù)交互侠仇。

引用關(guān)系是app引用logincomponent和mincomponent轻姿,然后logincomponent和mincomponent引用componentlib犁珠;

2、項(xiàng)目中的配置
(1)在gradle.properties配置全局gradle環(huán)境


在這里插入圖片描述

(2)app build.gradle的配置


在這里插入圖片描述

在這里插入圖片描述

logincomponent和minecomponent配置一樣互亮,下面是logincomponent build.gradle的配置
在這里插入圖片描述

在這里插入圖片描述

注意這里必須要配置兩個AndroidManifest.xml犁享,一個用于獨(dú)立運(yùn)行,一個用做組件


在這里插入圖片描述

獨(dú)立運(yùn)行下的AndroidManifest如下:
在這里插入圖片描述

作為組件的AndroidManifest.xml如下
在這里插入圖片描述

minecomponent組件的配置一樣豹休,這里就不貼圖了炊昆。

代碼實(shí)現(xiàn)

配置完成后,我們就可以開始重頭戲威根,擼代碼凤巨。
組件化的時候我們首先要解決兩個問題
1、回頭看下項(xiàng)目結(jié)構(gòu)圖洛搀,由于App引用了logincomponent和minecomponent這個兩個組件敢茁,而他們兩個作為組件要怎么拿到App的Application這個項(xiàng)目唯一的全局上下文。
2姥卢、組件間如何通訊卷要。

組件獲取到上下文

對于問題1,我們可以在App層独榴,通過反射將Application設(shè)置到兩個組建中僧叉,這樣組件就可以使用到App層的Application。
(1)首先在componentlib中定義接口IAppComponent:

public interface IAppComponent {
    void initializa(Application application);
}

(2)然后組件的Application繼承該接口:

public class LoginApp extends Application implements IAppComponent {
    private static Application application;

    public static Application getApplicatoin(){
        return application;
    }

    //獨(dú)立運(yùn)行的時候棺榔,這里是入口
    @Override
    public void onCreate() {
        super.onCreate();
        initializa(this);
    }
    //這里獲取到了全局的Application
    @Override
    public void initializa(Application application) {
        this.application = application;
    }
}

注意上面的onCreate()方法瓶堕,作為組件的時候是不會被調(diào)用的,但是獨(dú)立運(yùn)行的時候onCreate就是入口了症歇。

(3)App的Application通過反射進(jìn)行組件的實(shí)例化

public class AppConfig {
    public static final String[] COMPONENTS = {
            "com.kangjj.logincomponent.LoginApp",
            "com.kangjj.manecomponent.MineApp"
    };
}
public class MainApp extends Application implements IAppComponent {
    private MainApp application;

    public MainApp getApplication() {
        return application;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initializa(this);
    }
    //組件實(shí)例化
    @Override
    public void initializa(Application application) {
        this.application = (MainApp) application;
        for (String component : AppConfig.COMPONENTS) {
            try {
                //通過反射獲取到需要實(shí)例化的組件
                Class<?> clazz = Class.forName(component);  
                Object object = clazz.newInstance();
                //將實(shí)例化的對象強(qiáng)轉(zhuǎn)為IAppComponent郎笆,然后進(jìn)行組件的實(shí)例化
                if(object instanceof  IAppComponent){
                    ((IAppComponent)object).initializa(application);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

這樣組件就獲取到了全局的Application

組件通訊

在這里插入圖片描述

(1)創(chuàng)建組件的通訊工廠管理類

public class ServiceFactory {
    private static final ServiceFactory instance = new ServiceFactory();
    public static ServiceFactory getInstance(){
        return instance;
    }
    private ServiceFactory(){}

    private ILoginService mLoginService;
    private IMineService mMineService;

    public ILoginService getLoginService() {
        //如果將登陸組件移除,App進(jìn)行無登陸組件的業(yè)務(wù)
        if(mLoginService == null){
            mLoginService = new EmptyLoginService();
        }
        return mLoginService;
    }
    
    //設(shè)置logincomponent的通訊接口忘晤,在logincomponent的初始化的地方實(shí)現(xiàn)
    public void setLoginService(ILoginService loginService) {
        this.mLoginService = loginService;
    }

    public IMineService getMineService() {
        if(mMineService == null){
            mMineService = new EmptyMineService();
        }
        return mMineService;
    }

    public void setMineService(IMineService mineService) {
        this.mMineService = mineService;
    }
}

(2)定義ILoginService接口,該接口主要是用于兩個類的實(shí)現(xiàn):

public interface ILoginService {
    void launch(Context context, String targetClass);

    Fragment newUserInfoFragment(FragmentManager fragmentManager, int viewId, Bundle bundle);
}

logincomponent里的LoginService,這里進(jìn)行啟動組件的Actiivty宛蚓,或者獲取Fragment等操作。

public class LoginService implements ILoginService {
    @Override
    public void launch(Context context, String targetClass) {
        Intent intent = new Intent(context,LoginActivity.class);
        intent.putExtra(LoginActivity.EXTRA_TARGET_CLASS,targetClass);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    @Override
    public Fragment newUserInfoFragment(FragmentManager fragmentManager, int viewId, Bundle bundle) {
        UserInfoFragment fragment = new UserInfoFragment();
        fragment.setArguments(bundle);
        fragmentManager.beginTransaction().add(viewId,fragment).commit();
        return fragment;
    }

}

componentlib的EmptyLoginService

public class EmptyLoginService implements ILoginService {
    @Override
    public void launch(Context context, String targetClass) {
    }

    @Override
    public Fragment newUserInfoFragment(FragmentManager fragmentManager, int viewId, Bundle bundle) {
        return null;
    }
}

(3)接下來是初始化LoginService

public class LoginApp extends Application implements IAppComponent {
    ···
    //省略一些代碼
    
    @Override
    public void initializa(Application application) {
        this.application = application;
        ServiceFactory.getInstance().setLoginService(new LoginService());
    }
}

(4)調(diào)用设塔,進(jìn)行通訊
下面是LoginActivity和UserInfoFragment凄吏,前者在App進(jìn)行啟動調(diào)用,后者用來獲取Fragment在App進(jìn)行顯示闰蛔。

public class LoginActivity extends AppCompatActivity {
    public static final String EXTRA_TARGET_CLASS = "EXTRA_TARGET_CLASS";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
    }
}

public class UserInfoFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.userinfo_fragment,null);
    }
}

對應(yīng)的布局文件是很簡單的TextView痕钢,這里就不顯示了。

App里MainActivity的使用:

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //跳轉(zhuǎn)到登錄頁面
    public void login(View view){
        ServiceFactory.getInstance().getLoginService().launch(this,"");
    }

    public void goMine(View view){
        ServiceFactory.getInstance().getMineService().launch(this,2 ,REQUEST_CODE);
    }
    //獲取組件的Userinfo頁面進(jìn)行顯示
    public void getFragment(View view){
        ServiceFactory.getInstance().getLoginService().newUserInfoFragment(getSupportFragmentManager(),R.id.container,new Bundle());
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==REQUEST_CODE && resultCode==RESULT_OK){
            Toast.makeText(this,data.getStringExtra("fromMine"),Toast.LENGTH_LONG).show();
        }
    }
}

對應(yīng)xml布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="login"
        android:onClick="login"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="go mine"
        android:onClick="goMine"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="get UserInfoFragment"
        android:onClick="getFragment"/>

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

(5)運(yùn)行序六,運(yùn)行前記得修改gradle.properties

#login組件獨(dú)立運(yùn)行的標(biāo)志
loginRunAlone = false
#mine組件獨(dú)立運(yùn)行的標(biāo)志
mineRunAlone = false

然后編譯任连、運(yùn)行。

總結(jié)

組件化并不復(fù)雜例诀,本質(zhì)上也是gradle配置的變化而已随抠,組件化核心思想是面向接口變成裁着,定義公公組件,所有組件都依賴公共組件拱她。

代碼入口

文章有何不妥之處跨算,歡迎指正,與討論椭懊,謝謝觀看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末步势,一起剝皮案震驚了整個濱河市氧猬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坏瘩,老刑警劉巖盅抚,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晨另,死亡現(xiàn)場離奇詭異箫措,居然都是意外死亡祥绞,警方通過查閱死者的電腦和手機(jī)啼肩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門怖现,熙熙樓的掌柜王于貴愁眉苦臉地迎上來线得,“玉大人流椒,你說我怎么就攤上這事垦巴∪老铮” “怎么了邑彪?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胧华。 經(jīng)常有香客問我寄症,道長,這世上最難降的妖魔是什么矩动? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任有巧,我火速辦了婚禮,結(jié)果婚禮上悲没,老公的妹妹穿的比我還像新娘篮迎。我一直安慰自己,他們只是感情好檀训,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布柑潦。 她就那樣靜靜地躺著,像睡著了一般峻凫。 火紅的嫁衣襯著肌膚如雪渗鬼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天荧琼,我揣著相機(jī)與錄音譬胎,去河邊找鬼差牛。 笑死,一個胖子當(dāng)著我的面吹牛堰乔,可吹牛的內(nèi)容都是我干的偏化。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼镐侯,長吁一口氣:“原來是場噩夢啊……” “哼侦讨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苟翻,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤韵卤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后崇猫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沈条,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年诅炉,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜡歹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡涕烧,死狀恐怖月而,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情议纯,我是刑警寧澤景鼠,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站痹扇,受9級特大地震影響铛漓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鲫构,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一浓恶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧结笨,春花似錦包晰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赫模,卻和暖如春树肃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瀑罗。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工胸嘴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雏掠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓劣像,卻偏偏與公主長得像乡话,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耳奕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容