Android組件化

在項(xiàng)目開發(fā)過程中,隨著業(yè)務(wù)與人員的增加惩琉,如果沒有提前使用合理的架構(gòu),代碼會變得越來越臃腫夺荒,功能耦合性也越來越高瞒渠。為了代碼的質(zhì)量良蒸,這時候我們需要對工程進(jìn)行重構(gòu)。

比較簡單的重構(gòu)方案就是代碼按照模塊劃分伍玖,也就是Android中的module概率嫩痰,每一個module對應(yīng)一個模塊,各自的代碼在各自的module中提交窍箍,這種情況下是合理的串纺,但是不同的模塊有可能涉及相同的功能。

比如一個模塊有購買功能仔燕,而另一個模塊也有購買功能造垛,這時候無論把購買功能的代碼放到哪個模塊都不能避免模塊間的耦合,因此為了解決這種問題就有了組件化的思想晰搀。

一五辽、組件化與模塊化的對比

模塊化:模塊化對應(yīng)的是獨(dú)立業(yè)務(wù)模塊。
組件化:組件化是單一的功能組件外恕,每一個都可以使用module開發(fā)并可以提供sdk發(fā)布使用杆逗。
對比而言模塊化是業(yè)務(wù)為導(dǎo)向,而組件化是功能為導(dǎo)向鳞疲。
模塊可以包含多個組件罪郊,因此模塊化的顆粒度明顯大于組件化。
兩者的目的都是一致的都是為了工程的解耦尚洽。

組件化基礎(chǔ)架構(gòu)圖:
組件化.png

上面是最簡單的組件化架構(gòu)圖
base是基類悔橄,集成常用的基礎(chǔ)框架
login/pay是組件module,依賴base腺毫,提供各種的功能需求癣疟。
最上層的app是模塊或者主工程,依賴這三個組件完成業(yè)務(wù)的需求潮酒。

二睛挚、組件化面臨的問題

組件化開發(fā)過程中需要面臨的幾個問題:

  • 1.工程中每個組件都需要獨(dú)立運(yùn)行的能力也需要被集成到主工程運(yùn)行,這樣也能夠提升組件的編譯與運(yùn)行速度急黎,節(jié)省開發(fā)時間扎狱。
  • 2.組件間的數(shù)據(jù)傳遞與方法的調(diào)用,這是我們需要解決的勃教。因?yàn)榻M件之間可能需要狀態(tài)判定淤击,比如購買需要登錄的狀態(tài),那么如何優(yōu)雅的獲取組件的數(shù)據(jù)是一個問題故源。
  • 3.如何在不強(qiáng)依賴的情況下優(yōu)雅的完成組件之間界面的跳轉(zhuǎn)污抬。
  • 4.如何在不強(qiáng)依賴的情況下優(yōu)雅的完成組件Fragment的在主工程的創(chuàng)建
  • 5.主工程集成調(diào)試時,如何在不依賴工程的情況下主工程也不報錯心软。
  • 6.如何實(shí)現(xiàn)代碼隔離壕吹,資源隔離

三著蛙、組件實(shí)現(xiàn)獨(dú)立調(diào)試與集成調(diào)試

Android Studio使用Gradle構(gòu)建工程,Gradle提供了三種插件耳贬,通過配置不同的插件構(gòu)建不同的工程踏堡。

  • App 插件,id: com.android.application
  • Library 插件咒劲,id: com.android.library
  • Test 插件顷蟆,id: com.android.test

base是純粹的library,因此只需要引用library即可

apply plugin: 'com.android.library'

llogin/pay組件則需要做些額外的配置腐魂,因?yàn)樗鼈冃枰С旨傻絘pp項(xiàng)目中帐偎,也需要能夠支持獨(dú)立運(yùn)行。
在集成到主工程中時蛔屹,它們是library削樊。獨(dú)立運(yùn)行時,它們是Application兔毒。因此就需要配置一個字段來設(shè)置該工程是否是library還是Application漫贞。

3.1動態(tài)配置插件與applicationId

在module中新增gradle.properties文件,文件中配置

isRunAlone = false //true代表獨(dú)立運(yùn)行育叁,false代表是以library形式運(yùn)行迅脐。

在module的build.gradle文件中,依賴插件語句針對新增字段做判斷:

if (isRunAlone.toBoolean()) {
    //獨(dú)立運(yùn)行
    apply plugin: 'com.android.application'
} else {
    //依賴主工程運(yùn)行
    apply plugin: 'com.android.library'
}

同時豪嗽,作為library不需要applicationId谴蔑,所以需要在配置中增加對新增字段的處理:

defaultConfig {
   if (isRunAlone.toBoolean()) {
      applicationId "com.xj.paymodule"
   }
   ...
}
3.2動態(tài)配置manifest文件

module在獨(dú)立運(yùn)行時有自己的主入口文件,在集成到主工程時如果不處理龟梦,主工程合并manifest后會導(dǎo)致多個入口出現(xiàn),這是個問題隐锭。
因此我們需要額外一份manifest文件,然后在gradle文件中動態(tài)配置manifest調(diào)用。
如圖:


組件化manifest.png

主工程依賴:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xj.loginmodule">

    <application
        android:theme="@style/AppTheme">
        <activity android:name="com.xj.loginmodule.LoginActivity">
        </activity>
    </application>

</manifest>

獨(dú)立運(yùn)行:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xj.loginmodule">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name=".LoginApp"
        android:theme="@style/AppTheme">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

在gradle中動態(tài)配置manifest.srcFile屬性

android{
...
sourceSets {
        main{
            if (isRunAlone.toBoolean()){
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }
...
}

此時組件化的第一步已經(jīng)完成了变秦。這時候通過更改新增屬性的值就可以動態(tài)的配置module獨(dú)立運(yùn)行或者依賴主工程運(yùn)行成榜。

四框舔、組件間數(shù)據(jù)傳遞與方法的相互調(diào)用

這時候我們需要考慮另一個問題,組件之間通信問題蹦玫。比如pay組件在購買時需要判斷登錄狀態(tài),那么就需要獲取login組件的狀態(tài)刘绣。當(dāng)然我們可以通過直接依賴的形式來處理樱溉,但是這就違背了組件相互獨(dú)立的原則,所以需要我們解決這個問題纬凤。這里我們采用了接口的方式來解決這個問題福贞。

4.1新增componsentbase工程

新增名為componsentbase的module工程,定義Service接口停士。創(chuàng)建Factory類,為所有Service提供調(diào)用方法挖帘。
工程圖如下:

componsentBase工程圖.png

public interface IAccountService {
    boolean isLogin();
    String getAccountId();
}

public class ServiceFactory {

    private IAccountService mAccountService;


    private ServiceFactory(){
    }

    public static ServiceFactory getInstance(){
        return Inner.serviceFactory;
    }

    private static class Inner {
        private static ServiceFactory serviceFactory = new ServiceFactory();
    }

    public void setAccountService(IAccountService service){
        mAccountService = service;
    }

    public IAccountService getAccountService(){
        if (null == mAccountService){
            mAccountService = new EmptyAccountService();
        }
        return mAccountService;
    }
}
4.2組件負(fù)責(zé)接口的實(shí)現(xiàn)完丽,并完成設(shè)值

LoginModule中提供接口的實(shí)現(xiàn),并在Application中完成注冊:

//實(shí)現(xiàn)
public class LoginService implements IAccountService {
    @Override
    public boolean isLogin() {
        return null != AccountUtils.getInstance().getAccountInfo();
    }

    @Override
    public String getAccountId() {
        return null == AccountUtils.getInstance().getAccountInfo() ? "" :
                AccountUtils.getInstance().getAccountInfo().accountId;
    }
}
//注冊
public class LoginApp extends Application {
  @Override
  public void onCreate() {
     ServiceFactory.getInstance().setAccountService(new LoginService());
  }
4.3組件之間調(diào)用

pay組件調(diào)用:

   if (ServiceFactory.getInstance().getAccountService().isLogin()){
       Toast.makeText(PayActivity.this,"購買成功",Toast.LENGTH_LONG).show();
   } else {
       Toast.makeText(PayActivity.this,"請先登錄",Toast.LENGTH_LONG).show();
   }

通過這樣的方法完成組件間交互拇舀,避免組件之間相互依賴逻族。這時候我們發(fā)現(xiàn)一個問題,LoginApp組件只有在獨(dú)立運(yùn)行的時候才會調(diào)用骄崩,集成運(yùn)行時聘鳞,組件的Application是主工程的Application,因此我們需要處理一下集成運(yùn)行時各組件Application配置注冊的問題要拂。
我們采用反射的技術(shù)來完成各組件Application動態(tài)配置的問題抠璃。
在componsentbase工程中創(chuàng)建BaseApp類,該類是抽象類繼承Application。

4.4組件Application數(shù)據(jù)初始化
public abstract class BaseApp extends Application {
    public abstract void initModuleConfig(Application application);
    public abstract void initModileData(Application application);
}

提供了初始化的方法脱惰。
主工程Application和組件Application都繼承BaseApp,并實(shí)現(xiàn)其抽象方法搏嗡。接口的注冊等相關(guān)調(diào)用都移入到實(shí)現(xiàn)方法中。
Login組件:

public class LoginApp extends BaseApp {
    private static final String TAG = "LoginApp";
    @Override
    public void onCreate() {
        super.onCreate();
        //獨(dú)立運(yùn)行會調(diào)用
        initModuleConfig(this);
    }

    @Override
    public void initModuleConfig(Application application) {
        ServiceFactory.getInstance().setAccountService(new LoginService());
    }

    @Override
    public void initModileData(Application application) {
    }
}

主工程:
主工程中配置需要反射調(diào)用的類名拉一。然后在Application中反射調(diào)用彻况。

public class MainApp extends BaseApp {
    private static final String TAG = "MainApp";
    //組件的application名稱
    private static final String LoginApp = "com.xj.loginmodule.LoginApp";
    //配置需要反射application數(shù)組
    public static String[] moudleApps = {LoginApp};

    @Override
    public void onCreate() {
        super.onCreate();
        //調(diào)用
        initModuleConfig(this);
        initModileData(this);
    }

    @Override
    public void initModuleConfig(Application application) {
        //反射調(diào)用initModuleConfig方法
        for (String appName : moudleApps){
            try {
                Class<?> appClass = Class.forName(appName);
                BaseApp baseApp = (BaseApp) appClass.newInstance();
                baseApp.initModuleConfig(application);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void initModileData(Application application) {
        //反射調(diào)用initModileData方法
        for (String appName : moudleApps){
            try {
                Class<?> appClass = Class.forName(appName);
                BaseApp baseApp = (BaseApp) appClass.newInstance();
                baseApp.initModileData(application);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行日志:

=====MainApp    onCreate =====
=====MainApp    initModuleConfig =====
=====LoginApp    initModuleConfig =====
=====MainApp    initModileData =====
=====LoginApp    initModileData =====

這樣不通過強(qiáng)依賴的就完成了依賴組件的配置。當(dāng)然也有缺點(diǎn)就是需要手動配置依賴的Application名稱舅踪。

到現(xiàn)在纽甘,組件化的搭建就已經(jīng)基本完成了。我們可以通過主工程運(yùn)行一下:

findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,LoginActivity.class);
                startActivity(intent);
            }
        });

findViewById(R.id.pay).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, PayActivity.class);
                startActivity(intent);
            }
        });
正常抽碌!

五烁兰、組件間之間優(yōu)雅的調(diào)用

測試代碼是通過顯示意圖來實(shí)現(xiàn)界面間的跳轉(zhuǎn),顯然不符合我們解耦的需要。畢竟組件支持可配置固逗,如果當(dāng)前主工程不依賴組件,那么工程就會編譯報錯這是不能容忍的晨仑。當(dāng)前我們可以使用隱式意圖,但是隱示式圖需要通過manifest文件管理痴颊,協(xié)作比較麻煩赏迟,所以我們采用更靈活的方式,使用開源的 ARouter 來實(shí)現(xiàn)蠢棱。

一個用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由锌杀、通信、解耦

路由是指從一個接口獲取數(shù)據(jù)包泻仙,從數(shù)據(jù)包中獲取路由包的目的路徑并進(jìn)行定向轉(zhuǎn)發(fā)到另一個接口的過程糕再。因此可以用來組件化解耦。
要進(jìn)行ARoute跳轉(zhuǎn)就需要進(jìn)行組件對ARoute依賴玉转,組件依賴于Base組件突想,所以我們需要在Base組件中依賴ARoute。

5.1ARoute組件引入

ARoute依賴:
Base工程:

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
    ...
}

dependencies {
    ...
    api 'com.alibaba:arouter-api:1.5.0'
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}

其余任何依賴需要使用ARouter的module:

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
    ...
}

dependencies {
    ...
    //需要 不然生成不了路由表
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
}
5.2ARoute組件初始化:

主工程Application中:

public class MainApp extends BaseApp {
    private static final String TAG = "MainApp";
    @Override
    public void onCreate() {
        super.onCreate();
        ...
        if (isDebug()) {           // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
            ARouter.openLog();     // 打印日志
            ARouter.openDebug();   // 開啟調(diào)試模式(如果在InstantRun模式下運(yùn)行猾担,必須開啟調(diào)試模式袭灯!線上版本需要關(guān)閉,否則有安全風(fēng)險)
        }
        ARouter.init(this);
        ...
    }
}
5.3ARoute組件路由注冊

路由注冊:
通過注解Route,path中/xx/xx為路徑 主要路徑至少兩級。

@Route(path = "/login/login")
public class LoginActivity extends AppCompatActivity {}

@Route(path = "/pay/pay")
public class PayActivity extends AppCompatActivity {}
5.4組件跳轉(zhuǎn):
通過path跳轉(zhuǎn)
findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build("/login/login").navigation();
            }
        });

findViewById(R.id.pay).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build("/pay/pay").navigation();
            }
        });

這樣就完成主工程與組件之間的解耦绑嘹。

5.5路由過濾攔截功能

pay模塊調(diào)用時要依賴login狀態(tài)妓蛮,登錄成功時,則進(jìn)行pay圾叼,否則中斷調(diào)用蛤克。
下面針對此業(yè)務(wù)編寫簡單的攔截器:

// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件,這樣就不需要在目標(biāo)頁重復(fù)做登陸檢查
// 攔截器會在跳轉(zhuǎn)之間執(zhí)行夷蚊,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8,name = "登錄攔截器")
public class LoginInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        //callback.onContinue(postcard);  // 處理完成构挤,交還控制權(quán)
        // callback.onInterrupt(new RuntimeException("我覺得有點(diǎn)異常"));      // 覺得有問題,中斷路由流程

        // 以上兩種至少需要調(diào)用其中一種惕鼓,否則不會繼續(xù)路由
        if (TextUtils.equals(postcard.getPath(),"/pay/pay")){
            if (ServiceFactory.getInstance().getAccountService().isLogin()){
                callback.onContinue(postcard);  // 處理完成筋现,交還控制權(quán)
            } else {
                callback.onInterrupt(new RuntimeException("請先登錄"));// 覺得有問題,中斷路由流程
            }
        } else {
            callback.onContinue(postcard);  // 處理完成箱歧,交還控制權(quán)
        }
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化矾飞,會在sdk初始化的時候調(diào)用該方法,僅會調(diào)用一次
    }
}

攔截器需要實(shí)現(xiàn)IInterceptor呀邢,重寫process方法洒沦。priority是優(yōu)先級,name是攔截器描述价淌。攔截器在跳轉(zhuǎn)之間調(diào)用申眼。觸發(fā)process方法后,callback.onContinue和 callback.onInterrupt必須要調(diào)用一個蝉衣,不然不會繼續(xù)路由括尸。上面代碼判斷是否是pay跳轉(zhuǎn),是的話則判斷是否登錄病毡,登錄則繼續(xù)路由濒翻,否則中斷路由。

六啦膜、組件之間優(yōu)雅的獲取Fragment

除了Activity有送,Android中也有需要Fragment的獲取。通常情況我們會直接new Fragment來引用功戚。但是現(xiàn)在為了主工程與組件之間的解耦娶眷,直接new的方式就不適用了似嗤。因此我們需要額外的方式去實(shí)現(xiàn)啸臀。ARouter新版本支持Fragment路由,因此我們可以直接使用ARouter做處理:

Fragment類添加@Route注解
@Route(path = "/login/loginFragment")
public class LoginFragment extends Fragment {}
通過路由獲取Fragment實(shí)例
Fragment fragment = (Fragment) ARouter.getInstance().build("/login/loginFragment").navigation();

獲取到實(shí)例后續(xù)操作和以前一樣。

這樣就完成了Fragment與主工程的解耦乘粒。除了借助ARouter組件外我們還可以通過接口的方式來解耦豌注。
原先接口中新增方法

public interface IAccountService {
    ...
    void newFragment(Context context, int containId, FragmentManager manager, Bundle bundle,String tag);
}

實(shí)現(xiàn)類中處理附載Fragment的邏輯

public class LoginService implements IAccountService {
    ...
    @Override
    public void newFragment(Context context, int containId, FragmentManager manager, Bundle bundle, String tag) {
        FragmentTransaction fragmentTransaction = manager.beginTransaction();
        LoginFragment fragment = new LoginFragment();
        fragment.setArguments(bundle);
        fragmentTransaction.add(containId,fragment,tag);
        fragmentTransaction.commitNow();
    }
}

主工程調(diào)用:

ServiceFactory.getInstance().getAccountService().newFragment(this,R.id.content,getSupportFragmentManager(),null,"");

這樣也完成了解耦并且保證了主工程編譯不會失敗。

七灯萍、主工程集成調(diào)試

到現(xiàn)在工程解耦基本上完成了轧铁,集成調(diào)試的問題在上面幾個問題中已經(jīng)解決了。通過componsentBase組件的各個Service接口解決了直接引用類的問題旦棉,并且保證主工程能夠訪問依賴的工程齿风。通過ARouter組件進(jìn)行組件之間的跳轉(zhuǎn)與獲取Fragment。這樣組件之間沒有強(qiáng)依賴绑洛,所以即便主工程不依賴組件救斑,主工程也不會編譯失敗。

七真屯、代碼隔離與資源隔離

我們面向接口編程方式來完成工程解耦的脸候,但是主工程還是能訪問到組件的代碼。那么就有可能有意無意的直接引用到組件的類绑蔫,這樣一來上面的解耦就白做了运沦。所以我們想要主工程只有在打包階段才能訪問組件的代碼,在開發(fā)階段不能訪問配深,這樣就杜絕了直接引用的存在携添。
這個問題我們通過gradle去實(shí)現(xiàn),gradle3.0提供了新的依賴方式runtimeOnly篓叶,同過runtimeOnly依賴的組件只有在運(yùn)行時對工程和消費(fèi)者可用薪寓,開發(fā)階段完全隔離。

所以我們需要在主工程依賴時采用runtimeOnly方式:

dependencies {
    ...
    runtimeOnly project(path: ':payModule')
    runtimeOnly project(path: ':loginModule')
    runtimeOnly project(path: ':livemodule')
}

這樣主工程就不會引用到組件的代碼了澜共。
在gradle版本中這樣雖然避免的了代碼引用向叉,但是資源確還是能夠引用的。新的版本上主工程引用組件的資源會報紅嗦董,因此新版本基本上已經(jīng)達(dá)到資源隔離了母谎。那么我們看下老版本gradle的做法:

android {
    ...
    resourcePrefix "login_"
    ...
    }

通過在組件的gradle中配置resourcePrefix字段來添加資源前綴。當(dāng)資源不以前綴開頭的話則資源會報紅京革,而以前綴開頭則正常奇唤。
但是resourcePrefix只能限制xml中的資源并不能限制圖片資源,因此我們針對圖片資源需要手動設(shè)置前綴,同時將共用的資源放入到Base庫中匹摇,這樣最大化的實(shí)現(xiàn)資源的隔離咬扇。
到這里組件化基本上結(jié)束了。最終結(jié)構(gòu)變成了如下所示:


總結(jié).png

本文是針對組件化學(xué)習(xí)的記錄廊勃,主要參考以下文章懈贺,感謝作者:
組件化最佳實(shí)踐

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末经窖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子梭灿,更是在濱河造成了極大的恐慌画侣,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堡妒,死亡現(xiàn)場離奇詭異配乱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)皮迟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門搬泥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伏尼,你說我怎么就攤上這事佑钾。” “怎么了烦粒?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵休溶,是天一觀的道長。 經(jīng)常有香客問我扰她,道長兽掰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任徒役,我火速辦了婚禮孽尽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忧勿。我一直安慰自己杉女,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布鸳吸。 她就那樣靜靜地躺著熏挎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晌砾。 梳的紋絲不亂的頭發(fā)上坎拐,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音养匈,去河邊找鬼哼勇。 笑死,一個胖子當(dāng)著我的面吹牛呕乎,可吹牛的內(nèi)容都是我干的积担。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼猬仁,長吁一口氣:“原來是場噩夢啊……” “哼帝璧!你這毒婦竟也來了先誉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤聋溜,失蹤者是張志新(化名)和其女友劉穎谆膳,沒想到半個月后叭爱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撮躁,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年买雾,在試婚紗的時候發(fā)現(xiàn)自己被綠了把曼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡漓穿,死狀恐怖嗤军,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晃危,我是刑警寧澤叙赚,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站僚饭,受9級特大地震影響震叮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍鸵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一苇瓣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧偿乖,春花似錦击罪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至画切,卻和暖如春损话,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槽唾。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工丧枪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庞萍。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓拧烦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钝计。 傳聞我的和親對象是個殘疾皇子恋博,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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

  • 1齐佳、組件化和插件化區(qū)別: 組件化開發(fā): (1)、組件化是將一個app分成多個Module债沮,每個Module都是一個...
    小紅軍storm閱讀 2,149評論 2 4
  • (轉(zhuǎn)載) Android組件化方案已經(jīng)開源炼吴,參見Android組件化方案開源。方案的解讀文章是一個小的系列疫衩,這是系...
    江左灬梅郎閱讀 2,780評論 2 31
  • 在目前移動互聯(lián)網(wǎng)時代硅蹦,每個 APP 就是流量入口,與過去 PC Web 瀏覽器時代不同的是闷煤,APP 的體驗(yàn)與迭代速...
    斜杠時光閱讀 13,887評論 4 139
  • 組件化就是多module劃分業(yè)務(wù)和基礎(chǔ)功能童芹。這樣可解耦,提高代碼復(fù)用鲤拿。在正式開始講解組件化之前可以先看一下一個組件...
    From64KB閱讀 2,179評論 0 9
  • 外面的蟲子叫的真歡 它們非要拉著我一起狂歡 我睡眼惺忪的點(diǎn)點(diǎn)頭 好吧好吧假褪,就和你們一起 它們又找來了隔壁的狗 狗好...
    胡八毛閱讀 238評論 2 1