GoRouter - Android App組件化改造的框架——支持模塊間的路由减细、通信流济、解耦,Gradle插件支持8.0+,模塊Application生命周期

GoRouter

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

簡(jiǎn)介

之前一直在用阿里開源的ARouter項(xiàng)目,因?yàn)锳Router多年未更新樟遣,ARouter開始有些不太適合了而叼,所以重新開發(fā)了這款A(yù)ndroid路由框架郭脂,同樣的API,更多的功能澈歉,遷移請(qǐng)參見文末8-11。

GoRouter和ARouter功能差異對(duì)比

功能 ARouter GoRouter 描述
自動(dòng)生成路由幫助類 不支持 支持 調(diào)用更方便屿衅,不需要知道目標(biāo)頁面參數(shù)名埃难、參數(shù)類型、是否必傳等元素涤久,通過幫助類提供的方法涡尘,可輕松實(shí)現(xiàn)路由跳轉(zhuǎn)和服務(wù)獲取,參見7-1
模塊Application生命周期 不支持 支持 主動(dòng)分發(fā)到業(yè)務(wù)模塊响迂,讓模塊無侵入的獲取Application生命周期考抄,參見6-1
路由頁面事件 不支持 支持 頁面事件解耦,提供更多蔗彤、更方便的API川梅,參見5-10
路由注冊(cè)方式 注解 注解、java GoRouter不僅提供了注解然遏,還能使用java方式注冊(cè)贫途,參見5-6
動(dòng)態(tài)注冊(cè)攔截器 不支持 支持 ARouter只能動(dòng)態(tài)注冊(cè)路由,不能動(dòng)態(tài)注冊(cè)攔截器
重寫跳轉(zhuǎn)URL服務(wù) 支持 不支持 可以在IPretreatmentService里實(shí)現(xiàn)相同功能待侵,參見8-10
獲取元數(shù)據(jù) 不支持 支持 有些場(chǎng)景需要判斷某個(gè)頁面當(dāng)前是否存在等需求丢早,就需要獲取頁面class等信息,參見5-1
inject(T) 單一 更多 ARouter不能在onNewIntent()方法里使用秧倾,也不能檢查required怨酝,GoRouter提供了更多使用場(chǎng)景,性能更好那先,參見4-2
按組分類农猬、按需初始化 支持 支持 ARouter不允許多個(gè)module中存在相同的分組,GoRouter允許
生成路由文檔 支持 支持 ARouter會(huì)生成多個(gè)模塊文檔胃榕,GoRouter提供Gradle任務(wù)一鍵生成項(xiàng)目總文檔盛险,更加豐富,參見5-9

一勋又、功能介紹

  1. 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)苦掘,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
  2. 支持多模塊工程使用
  3. 支持添加多個(gè)攔截器,自定義攔截順序
  4. 支持依賴注入楔壤,可單獨(dú)作為依賴注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射關(guān)系按組分類鹤啡、多級(jí)管理,按需初始化
  8. 支持用戶指定全局降級(jí)與局部降級(jí)策略
  9. 頁面蹲嚣、攔截器递瑰、服務(wù)等組件均自動(dòng)注冊(cè)到框架
  10. 支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫
  11. 支持獲取Fragment
  12. 完全支持Kotlin以及混編
  13. 支持第三方 App 加固
  14. 支持一鍵生成路由文檔
  15. 支持增量編譯
  16. 支持動(dòng)態(tài)注冊(cè)路由祟牲、路由組、服務(wù)和攔截器
  17. 支持模塊Application生命周期
  18. 支持路由頁面事件
  19. 自動(dòng)生成路由幫助類

二抖部、典型應(yīng)用

  1. 從外部URL映射到內(nèi)部頁面说贝,以及參數(shù)傳遞與解析
  2. 跨模塊頁面跳轉(zhuǎn),模塊間解耦
  3. 攔截跳轉(zhuǎn)過程慎颗,處理登陸乡恕、埋點(diǎn)等邏輯
  4. 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦

三俯萎、基礎(chǔ)功能

1. 添加依賴和配置
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

dependencies {
    api 'com.github.wyjsonGo.GoRouter:GoRouter-Api:2.5.2'
}
// Kotlin配置參見8-1
2. 在module項(xiàng)目下添加注解處理器依賴和配置
android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [GOROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    annotationProcessor 'com.github.wyjsonGo.GoRouter:GoRouter-Compiler:2.5.2'
}

module_user模塊Demo示例module_user/build.gradle

3. 添加路由
// 在支持路由的頁面上添加注解
// 這里的路徑需要注意的是至少需要有兩級(jí)傲宜,/xx/xx
@Route(path = "/test/activity")
public class TestActivity extend Activity {
    ...
}
4. 初始化SDK
if (BuildConfig.DEBUG) {
    GoRouter.openDebug(); // 開啟調(diào)試,查看路由詳細(xì)加載和跳轉(zhuǎn)過程日志
}
GoRouter.autoLoadRouteModule(this); // 盡可能早夫啊,推薦在Application中初始化
5. 發(fā)起路由操作

經(jīng)典方式:

// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)(通過URL跳轉(zhuǎn)在'進(jìn)階用法'中)
GoRouter.getInstance().build("/test/activity").go();

// 2. 跳轉(zhuǎn)并攜帶參數(shù)
GoRouter.getInstance().build("/test/fragment")
            .withString("name", "Wyjson")
            .withObject("test", new TestModel(123, "Jack"))
            .withInt("age", 35)
            .go();

Helper方式:(開啟Helper功能函卒,參見7-1)

// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)
TestActivityGoRouter.go();

// 2. 跳轉(zhuǎn)并攜帶參數(shù)
TestFragmentGoRouter.go("Wyjson", testModel, 35);
6. 使用Gradle插件實(shí)現(xiàn)路由表的自動(dòng)加載,支持Gradle8.0+
// 項(xiàng)目根目錄下的settings.gradle
pluginManagement {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
// 項(xiàng)目根目錄下的build.gradle
buildscript {
    dependencies {
        classpath 'com.github.wyjsonGo.GoRouter:GoRouter-Gradle-Plugin:2.5.2'
    }
}
// app目錄下的build.gradle
plugins {
    ...
    id 'com.wyjson.gorouter'
}
  • 支持Gradle8.0+撇眯。
  • 開發(fā)階段構(gòu)建加速參見5-8(最好在開發(fā)階段開啟,節(jié)省build時(shí)間)报嵌。
  • 可選使用,通過GoRouter提供的注冊(cè)插件進(jìn)行路由表的自動(dòng)加載叛本,默認(rèn)通過掃描dex的方式進(jìn)行加載(在運(yùn)行時(shí)注冊(cè),節(jié)省打包時(shí)間)沪蓬,通過gradle插件進(jìn)行自動(dòng)注冊(cè)可以縮短運(yùn)行時(shí)初始化時(shí)間(在打包時(shí)注冊(cè),節(jié)省運(yùn)行時(shí)間),解決應(yīng)用加固導(dǎo)致無法直接訪問dex文件来候。

四跷叉、進(jìn)階用法

1. 通過URL跳轉(zhuǎn)
// 新建一個(gè)Activity用于監(jiān)聽Scheme事件,之后直接把url傳遞給GoRouter即可(消息欄和外部所有URL跳轉(zhuǎn)都可以統(tǒng)一用這個(gè)Activity接收后分發(fā)跳轉(zhuǎn))
public class SchemeFilterActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Uri uri = getIntent().getData();
        GoRouter.getInstance().build(uri).go();
        finish();
    }
}

AndroidManifest.xml

<activity android:name=".activity.SchemeFilterActivity">
    <!-- Scheme -->
    <intent-filter>
        <data
        android:host="m.wyjson.com"
        android:scheme="gorouter"/>

        <action android:name="android.intent.action.VIEW"/>

        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
    </intent-filter>
</activity>
2. 解析參數(shù)

在帶有@Route注解的Activity、Fragment頁面使用@Param注解营搅,會(huì)自動(dòng)生成類名+$$Param.java注入類云挟,可調(diào)用inject()injectCheck()方法自動(dòng)注入?yún)?shù)。

// 為每一個(gè)參數(shù)聲明一個(gè)字段(不能是private)转质,并使用 @Param 標(biāo)注
// URL中不能傳遞Parcelable類型數(shù)據(jù)园欣,通過GoRouter api可以傳遞Parcelable對(duì)象
@Route(path = "/param/activity")
public class ParamActivity extends BaseParamActivity {

    @Param
    int age = 18;

    // 可以自定義參數(shù)name
    @Param(name = "nickname", remark = "昵稱", required = true)
    String name;

    /**
     * 使用 withObject 傳遞 List 和 Map 的實(shí)現(xiàn)了 Serializable 接口的實(shí)現(xiàn)類(ArrayList/HashMap)的時(shí)候,
     * 接收該對(duì)象的地方不能標(biāo)注具體的實(shí)現(xiàn)類類型應(yīng)僅標(biāo)注為 List 或 Map休蟹,
     * 否則會(huì)影響序列化中類型的判斷, 其他類似情況需要同樣處理
     */
    @Param(name = "test", remark = "自定義類型", required = true)
    TestModel testModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       // inject()方法會(huì)自動(dòng)對(duì)字段進(jìn)行賦值
       ParamActivity$$Param.inject(this);

       // 或使用

       // injectCheck()方法會(huì)自動(dòng)對(duì)字段進(jìn)行賦值沸枯,
       // 并檢查標(biāo)記@Param(required = true)的字段,
       // 檢查不通過會(huì)拋出ParamException()類型的異常,
       // 可通過e.getParamName()獲取參數(shù)名自行處理赂弓。
       try {
           ParamActivity$$Param.injectCheck(this);
       } catch (ParamException e) {
           Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
           finish();
           return;
       }

       Log.d("param", "base:" + base + "age:" + age + ",name:" + name);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        // inject()方法參數(shù)支持intent绑榴、bundle
        ParamActivity$$Param.inject(this, intent);
    }
}

// 支持父類字段自動(dòng)注入
public class BaseParamActivity extends Activity {
    @Param(remark = "我是一個(gè)父類字段")
    protected int base;
}

如果使用了withObject()方法,需要實(shí)現(xiàn)JSON服務(wù)

// 實(shí)現(xiàn)IJsonService接口
@Service(remark = "json服務(wù)")
public class JsonServiceImpl implements IJsonService {

    @Override
    public void init() {

    }

    @Override
    public String toJson(Object instance) {
        // 這里演示使用了gson,也可以使用其他json轉(zhuǎn)換工具
        return new Gson().toJson(instance);
    }

    @Override
    public <T> T parseObject(String input, Type typeOfT) {
        return new Gson().fromJson(input, typeOfT);
    }
}
3. 聲明攔截器(攔截跳轉(zhuǎn)過程盈魁,面向切面編程)
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件翔怎,這樣就不需要在目標(biāo)頁重復(fù)做登陸檢查
// 攔截器會(huì)在跳轉(zhuǎn)之前執(zhí)行,多個(gè)攔截器會(huì)按序號(hào)從小到大順序依次執(zhí)行
@Interceptor(ordinal = 1, remark = "測(cè)試攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Card card, InterceptorCallback callback) {
        ...
        callback.onContinue(card); // 處理完成,交還控制權(quán)
        // callback.onInterrupt(card, new RouterException("我覺得有點(diǎn)異常")); // 覺得有問題赤套,中斷路由流程

        // 以上兩種至少需要調(diào)用其中一種飘痛,否則不會(huì)繼續(xù)路由
    }

    @Override
    public void init() {
        // 攔截器的初始化,會(huì)在sdk初始化的時(shí)候調(diào)用該方法容握,僅會(huì)調(diào)用一次
    }
}
4. 處理跳轉(zhuǎn)結(jié)果
// 使用兩個(gè)參數(shù)的go方法宣脉,可以獲取單次跳轉(zhuǎn)的結(jié)果
GoRouter.getInstance().build("/test/activity").go(this, new GoCallback() {
    @Override
    public void onFound(Card card) {

    }

    @Override
    public void onLost(Card card) {

    }

    @Override
    public void onArrival(Card card) {

    }

    @Override
    public void onInterrupt(Card card,@NonNull Throwable exception) {

    }
});
5. 自定義全局降級(jí)策略
// 實(shí)現(xiàn)IDegradeService接口
@Service(remark = "全局降級(jí)策略")
public class DegradeServiceImpl implements IDegradeService {
   @Override
   public void onLost(Context context, Card card) {
       // do something.
   }

   @Override
   public void init() {

   }
}
6. 為目標(biāo)頁面聲明更多信息
// 我們經(jīng)常需要在目標(biāo)頁面中配置一些屬性,比方說"是否需要登陸"之類的
// 可以通過@Route注解的tag屬性進(jìn)行擴(kuò)展剔氏,這個(gè)屬性是一個(gè)int值脖旱,換句話說,單個(gè)int有4字節(jié)介蛉,可以配置31個(gè)開關(guān)
@Route(path = "/user/info/activity", tag = LOGIN | AUTHENTICATION)

// 在攔截器里通過Card對(duì)象拿到這個(gè)標(biāo)記進(jìn)行業(yè)務(wù)邏輯判斷
card.isTagExist(LOGIN); // 判斷是否存在登錄標(biāo)識(shí)

tag使用示例UserInfoActivity.java
tag處理示例SignInInterceptor.java

7. 通過依賴注入解耦:服務(wù)管理
// 暴露服務(wù)
// 聲明接口并繼承IService接口,其他組件通過接口來調(diào)用服務(wù)
public interface HelloService extends IService {
    String sayHello(String name);
}

// 實(shí)現(xiàn)接口
@Service(remark = "服務(wù)描述")
public class HelloServiceImpl implements HelloService {

    @Override
    public void init() {

    }

    @Override
    public String sayHello(String name) {
       return "hello, " + name;
    }
}

// 發(fā)現(xiàn)服務(wù)
HelloService helloService = GoRouter.getInstance().getService(HelloService.class);
if (helloService != null) {
    helloService.sayHello("Wyjson");
}

進(jìn)階用法溶褪,如需多個(gè)實(shí)現(xiàn)服務(wù)可指定@Service(alias = "xxx")

// 暴露支付服務(wù)
public interface PayService extends IService {
    String getPayType();
}

// 實(shí)現(xiàn)阿里支付接口
@Service(alias = "Alipay", remark = "AliPay服務(wù)")
public class AliPayServiceImpl implements PayService {
    @Override
    public void init() {

    }

    @Override
    public String getPayType() {
        return "AliPay";
    }
}

// 實(shí)現(xiàn)微信支付接口
@Service(alias = "WechatPay", remark = "微信Pay服務(wù)")
public class WechatPayServiceImpl implements PayService {
    @Override
    public void init() {

    }

    @Override
    public String getPayType() {
        return "WechatPay";
    }
}

// 發(fā)現(xiàn)阿里支付服務(wù)
PayService alipayService = GoRouter.getInstance().getService(PayService.class, "Alipay");
if (alipayService != null) {
    alipayService.getPayType();
}
// 發(fā)現(xiàn)微信支付服務(wù)
PayService wechatPayService = GoRouter.getInstance().getService(PayService.class, "WechatPay");
if (wechatPayService != null) {
    wechatPayService.getPayType();
}
8. 跳轉(zhuǎn)前預(yù)處理
// 比如跳轉(zhuǎn)登錄頁面币旧,只要簡(jiǎn)單的調(diào)用go就可以了,一些必須的參數(shù)和標(biāo)識(shí)可以放到預(yù)處理里來做猿妈。
// 或者一些埋點(diǎn)的處理
// 實(shí)現(xiàn)IPretreatmentService接口
@Service(remark = "預(yù)處理服務(wù)")
public class PretreatmentServiceImpl implements IPretreatmentService {

   @Override
   public void init() {

   }

   @Override
   public boolean onPretreatment(Context context, Card card) {
       // 登錄頁面預(yù)處理
       if ("/user/sign_in/activity".equals(card.getPath())) {
           card.withFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
       }
       return true; // 跳轉(zhuǎn)前預(yù)處理吹菱,如果需要自行處理跳轉(zhuǎn),該方法返回 false 即可
   }
}

五彭则、更多功能

1. 根據(jù)路徑獲取元數(shù)據(jù)
// 有些場(chǎng)景需要判斷某個(gè)頁面當(dāng)前是否存在等需求,就需要獲取頁面class等信息鳍刷,可以使用此方法getCardMete()
CardMeta cardMeta = GoRouter.getInstance().build("/user/info/activity").getCardMeta();
if (cardMeta != null) {
    cardMeta.getPathClass();
}
2. 詳細(xì)的API說明
// 標(biāo)準(zhǔn)的路由請(qǐng)求
GoRouter.getInstance().build("/main/activity").go();

// 通過Uri直接解析(外部、h5等調(diào)用native頁面攜帶參數(shù)可以使用此方式)
Uri uri = Uri.parse("/new/param/activity?age=9&name=jack&base=123");
GoRouter.getInstance().build(uri).go();

// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求俯抖,startActivityForResult()
// go的第一個(gè)參數(shù)必須是Activity输瓜,第二個(gè)參數(shù)則是RequestCode
// 當(dāng)然也支持registerForActivityResult()方法
GoRouter.getInstance().build("/main/activity").go(this, 5);

// 直接傳遞Bundle
Bundle params = new Bundle();
GoRouter.getInstance()
    .build("/main/activity")
    .with(params)
    .go();

// 指定Flag
GoRouter.getInstance()
    .build("/main/activity")
    .withFlags()
    .go();

// 獲取Fragment
Fragment fragment = (Fragment) GoRouter.getInstance().build("/test/fragment").go();

// 序列化對(duì)象傳遞
GoRouter.getInstance().build("/main/activity")
    .withSerializable("user",new User())
    .go();

// 自定義對(duì)象傳遞
GoRouter.getInstance().build("/main/activity")
    .withObject("test", new TestModel(123, "Jack"))
    .go();

// 覺得接口不夠多,可以直接拿出Bundle賦值
GoRouter.getInstance()
        .build("/main/activity")
        .getExtras();

// 轉(zhuǎn)場(chǎng)動(dòng)畫(常規(guī)方式)
GoRouter.getInstance()
    .build("/test/activity")
    .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
    .go();

// 轉(zhuǎn)場(chǎng)動(dòng)畫(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
    makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);

// ps. makeSceneTransitionAnimation 使用共享元素的時(shí)候芬萍,需要在go方法中傳入當(dāng)前Activity

GoRouter.getInstance()
    .build("/test/activity")
    .withActivityOptionsCompat(compat)
    .go();

// 使用綠色通道(跳過所有的攔截器)
GoRouter.getInstance().build("/main/activity").greenChannel().go();

// 使用自己的日志工具打印日志
GoRouter.setLogger();

// 使用自己提供的線程池
GoRouter.setExecutor();

// 開啟openDebug()后,打印日志的時(shí)候打印線程堆棧
GoRouter.printStackTrace();
3. 獲取原始的URI
String uriStr = GoRouter.getInstance().getRawURI(this);
4. 獲取當(dāng)前頁面路徑
String path = GoRouter.getInstance().getCurrentPath(this);
5. 獲取路由注冊(cè)模式
// true: Gradle插件進(jìn)行自動(dòng)注冊(cè)(在打包時(shí)注冊(cè),節(jié)省運(yùn)行時(shí)間)
// false: 掃描dex的方式(在運(yùn)行時(shí)注冊(cè),節(jié)省打包時(shí)間)
GoRouter.getInstance().isRouteRegisterMode();
6. java方式動(dòng)態(tài)注冊(cè)路由信息

適用于插件化架構(gòu)的App以及需要?jiǎng)討B(tài)注冊(cè)路由尤揣、路由組、服務(wù)和攔截器的場(chǎng)景柬祠。目標(biāo)頁面北戏、服務(wù)和攔截器可以不標(biāo)注@Route@Service漫蛔、@Interceptor注解嗜愈。

// 動(dòng)態(tài)注冊(cè)服務(wù)
GoRouter.getInstance().addService(UserServiceImpl.class);
// 動(dòng)態(tài)注冊(cè)服務(wù)多個(gè)實(shí)現(xiàn)
GoRouter.getInstance().addService(WechatPayServiceImpl.class, "WechatPay");
GoRouter.getInstance().addService(AliPayServiceImpl.class, "Alipay");

// 注冊(cè)攔截器(重復(fù)添加相同序號(hào)會(huì)catch)
GoRouter.getInstance().addInterceptor(1, TestInterceptor.class);

// 動(dòng)態(tài)注冊(cè)攔截器(重復(fù)添加相同序號(hào)會(huì)覆蓋(更新))
GoRouter.getInstance().setInterceptor(1, TestInterceptor.class);

// 動(dòng)態(tài)注冊(cè)路由分組,按需加載路由(注意:同一批次僅允許相同group的路由信息注冊(cè))
GoRouter.getInstance().addRouterGroup("show", new IRouteModuleGroup() {
    @Override
    public void load() {
        GoRouter.getInstance().build("/show/list/activity").commitActivity(ShowListActivity.class);
        GoRouter.getInstance().build("/show/info/fragment").commitFragment(ShowInfoFragment.class);
        ...
    }
});

// 當(dāng)然,你也可以直接注冊(cè)Activity、Fragment路由
// 動(dòng)態(tài)注冊(cè)Activity
GoRouter.getInstance().build("/user/info/activity").putTag(3).commitActivity(UserInfoActivity.class);
// 動(dòng)態(tài)注冊(cè)Fragment
GoRouter.getInstance().build("/new/param/fragment").putInt("age").putString("name").commitFragment(ParamFragment.class);

// 自動(dòng)注入?yún)?shù)(開啟混淆的情況下,使用此方法需要配置混淆規(guī)則,參見8-7)
GoRouter.getInstance().inject(this);
7. 自定義模塊路由加載

如不使用Gradle插件[3-6]進(jìn)行自動(dòng)注冊(cè)莽龟,也不想走默認(rèn)掃描dex的方式蠕嫁,可以不調(diào)用GoRouter.autoLoadRouteModule(this)方法,但需要自行調(diào)用模塊生成的路由加載類轧房。
模塊項(xiàng)目里至少使用一條注解@Route拌阴、@Service@Interceptor奶镶,就會(huì)生成對(duì)應(yīng)路由表的加載類迟赃。路由表加載類命名規(guī)則會(huì)根據(jù)GOROUTER_MODULE_NAME設(shè)置的模塊名稱轉(zhuǎn)換成大寫駝峰命名+$$Route.java陪拘,所有模塊生成的路由表加載類都會(huì)放到com.wyjson.router.module.route包下。
例如模塊名稱module_user會(huì)生成ModuleUser$$Route.java

// 可在任意地方調(diào)用模塊路由加載類
new ModuleUser$$Route().load();
8. Gradle插件自定義執(zhí)行的任務(wù)

由于在開發(fā)階段需要經(jīng)常build項(xiàng)目纤壁,每次運(yùn)行都走gradle插件自動(dòng)注冊(cè)后左刽,會(huì)導(dǎo)致gradle自帶任務(wù)dexBuilderDebug(轉(zhuǎn)換class文件為dex文件)會(huì)很耗時(shí),所以在開發(fā)階段最好忽略buildType等于debug的情況,debug的情況就會(huì)走默認(rèn)掃描dex方式注冊(cè)酌媒,節(jié)省開發(fā)build時(shí)間欠痴。

// app目錄下的build.gradle
plugins {
    ...
    id 'com.wyjson.gorouter'
}
// 不寫下面的配置,默認(rèn)android.buildTypes任務(wù)全部執(zhí)行自動(dòng)注冊(cè)秒咨。
GoRouter {
    // 允許執(zhí)行自動(dòng)注冊(cè)任務(wù)的集合
    runAutoRegisterBuildTypes "release", "sandbox", "more"
}
9. 生成路由文檔

使用gradle命令一鍵生成路由文檔

./gradlew generateRouteDocDebug

當(dāng)然你也可以使用圖形頁面執(zhí)行任務(wù)

gradle_task_generate_router_doc.png

生成的路由文檔會(huì)保存到項(xiàng)目下的/app/項(xiàng)目名-變體名-route-doc.json喇辽,Demo示例/app/GoRouter-release-route-doc.json

10. 路由頁面事件

在之前跨模塊頁面事件通知的流程是,使用EventBus庫雨席,在module_common模塊里定義event類菩咨,頁面注冊(cè)訂閱者接收事件,實(shí)現(xiàn)事件處理并注解標(biāo)識(shí)陡厘,頁面銷毀解除注冊(cè)抽米。
這一套流程下來步驟很多,會(huì)出現(xiàn)很多event類糙置,而且這些類只在一個(gè)頁面訂閱云茸,還要去module_common模塊里定義,發(fā)布基礎(chǔ)數(shù)據(jù)類型谤饭,會(huì)導(dǎo)致所有訂閱者都會(huì)收到标捺,也無法檢測(cè)頁面生命周期狀態(tài)。
顯然EventBus適合任意處發(fā)布多處訂閱的場(chǎng)景揉抵,而我們需要任意處發(fā)布一處訂閱的場(chǎng)景宜岛,這樣就可以訂閱基礎(chǔ)數(shù)據(jù)類型了,自定義類型也不需要再包裹一層新的Event類發(fā)布出去功舀。

典型場(chǎng)景

  1. 消息Fragment通知主頁Activity的tab刷新未讀消息數(shù)萍倡。
  2. 代替startActivityForResult(),獲取返回來的數(shù)據(jù)辟汰。
  3. Activity顯示其他模塊Dialog列敲,實(shí)時(shí)顯示Dialog里選擇的數(shù)據(jù)。

使用

// 在Activity/Fragment上訂閱String類型事件
GoRouter.getInstance().registerEvent(this, String.class, new Observer<String>() {
    @Override
    public void onChanged(String value) {
        // do something.
    }
});

// 在任意地方向MainActivity發(fā)送String類型數(shù)據(jù)
GoRouter.getInstance().postEvent("/main/activity", "Go!");

更多用法

// 訂閱自定義類型事件
GoRouter.getInstance().registerEvent(this, UserEntity.class, new Observer<UserEntity>() {
    @Override
    public void onChanged(UserEntity data) {
        // do something.
    }
});

// 向UserFragment發(fā)送自定義類型數(shù)據(jù)
GoRouter.getInstance().postEvent("/user/fragment", new UserEntity(89, "Wyjson"));

// 手動(dòng)解除String類型全部訂閱
GoRouter.getInstance().unRegisterEvent(this, String.class);
// 手動(dòng)解除String類型指定observer訂閱
GoRouter.getInstance().unRegisterEvent(this, String.class, observer);
  • registerEvent()registerEventForever()支持在Activity帖汞、Fragment上使用戴而。
  • registerEvent()方法只有頁面處于活躍狀態(tài)下才會(huì)收到,registerEventForever()方法無論頁面處于何種狀態(tài)下都會(huì)收到翩蘸。
  • postEvent()方法支持主線程和子線程調(diào)用所意。多次調(diào)用的情況下,在頁面生命周期處于onPause下,registerEvent()方法只會(huì)收到最后一次數(shù)據(jù)扶踊,registerEventForever()會(huì)全部收到泄鹏。在頁面生命周期處于onResume下,兩個(gè)方法會(huì)收到全部數(shù)據(jù)秧耗。
  • unRegisterEvent()方法無需主動(dòng)調(diào)用备籽,框架會(huì)在頁面銷毀時(shí)自動(dòng)解除訂閱,除非你想立刻取消訂閱分井。
  • 頁面可以訂閱多個(gè)相同類型的事件和多個(gè)不同類型的事件车猬。
  • 路由頁面事件功能內(nèi)部使用LiveData

Demo示例EventActivity.java

六尺锚、模塊Application生命周期

Application生命周期主動(dòng)分發(fā)到組件珠闰,讓模塊無侵入的獲得Application生命周期

1. 在模塊中添加@ApplicationModule注解,實(shí)現(xiàn)IApplicationModule接口
@ApplicationModule
public class UserApplication implements IApplicationModule {

    @Override
    public void onCreate(Application app) {
        // do something.
    }

    /**
     * 優(yōu)化啟動(dòng)速度,一些不著急的初始化可以放在這里做,子線程
     */
    @Override
    public void onLoadAsync(Application app) {
        // do something.
    }
}

Demo示例CommonApplication.java

2. 在主Application添加分發(fā)
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 調(diào)用callAMOnCreate()方法瘫辩,觸發(fā)模塊Application的onCreate()铸磅、onLoadAsync()方法。
        GoRouter.callAMOnCreate(this);
    }

Demo示例MyApplication.java

3. 進(jìn)階用法
  • 指定模塊Application優(yōu)先級(jí)杭朱,可以重寫setPriority()方法,它將按照從大到小的執(zhí)行順序執(zhí)行吹散。
  • IApplicationModule接口不僅提供了onCreate弧械、onLoadAsync方法,還提供了onTerminate空民、onConfigurationChanged刃唐、onLowMemoryonTrimMemory方法界轩,如需監(jiān)聽記得在主Application中添加對(duì)應(yīng)的分發(fā)方法GoRouter.callAMOnCreate()画饥、GoRouter.callAMOnTerminate()GoRouter.callAMOnConfigurationChanged(newConfig)浊猾、GoRouter.callAMOnLowMemory()抖甘、GoRouter.callAMOnTrimMemory(level)

七葫慎、自動(dòng)生成路由幫助類

開啟此功能可以大幅提升開發(fā)效率衔彻,避免錯(cuò)誤調(diào)用的情況。不需要知道目標(biāo)頁面參數(shù)名偷办、參數(shù)類型艰额、是否必傳等元素,通過幫助類提供的方法椒涯,可輕松實(shí)現(xiàn)路由跳轉(zhuǎn)和服務(wù)獲取柄沮。

1. 添加配置
// app目錄下的build.gradle添加配置
plugins {
    ...
    id 'com.wyjson.gorouter'
}
GoRouter {
    ...
    // 指定根模塊項(xiàng)目名稱,開啟自動(dòng)生成路由幫助類功能
    helperToRootModuleName "module_common"
}

指定helperToRootModuleName配置參數(shù)后會(huì)開啟自動(dòng)生成路由幫助類功能,這個(gè)參數(shù)需要指定一個(gè)根模塊項(xiàng)目名(指定的這個(gè)模塊項(xiàng)目只要保證其他業(yè)務(wù)模塊都能調(diào)用到种吸,并且頁面?zhèn)鬟f參數(shù)用到的實(shí)體類也能引用到就可以鸳址,推薦指向module_common模塊或者新建一個(gè)模塊單獨(dú)存放這些生成的路由幫助類)。

2. 使用
// 經(jīng)典:訪問/main/activity
GoRouter.getInstance().build("/main/activity").go();
// helper:訪問/main/activity(框架會(huì)根據(jù)path生成對(duì)應(yīng)類名GoRouter.java)
MainActivityGoRouter.go();

// 經(jīng)典:帶參數(shù)訪問
GoRouter.getInstance().build("/new/param/activity")
        .withString("nickname", "Wyjson")
        .withObject("test", testModel)
        .withInt("age", 78)
        .withInt("base", 7758)
        .go();
// helper:帶參數(shù)訪問
// 必傳參數(shù)
NewParamActivityGoRouter.go("Wyjson", testModel);
// 所有參數(shù)(必傳參數(shù)和非必傳參數(shù)一起)
NewParamActivityGoRouter.go("Wyjson", testModel, base, 78);
// 非必傳參數(shù)可以鏈?zhǔn)秸{(diào)用,解決了經(jīng)典方式需要知道類型和參數(shù)名的問題
NewParamActivityGoRouter.create("Wyjson", testModel)// 必傳參數(shù)
        .setAge(78)// 非必傳參數(shù)
        .setBase(7758)// 非必傳參數(shù)
        .build()
        .go();

// 經(jīng)典:訪問Fragment
Fragment fragment = (Fragment) GoRouter.getInstance().build("/user/card/fragment").go();
// helper:訪問Fragment
Fragment fragment = UserCardFragmentGoRouter.go();

// 經(jīng)典:獲取元數(shù)據(jù)
CardMeta cardMeta = GoRouter.getInstance().build("/user/info/activity").getCardMeta();
// helper:獲取元數(shù)據(jù)
CardMeta cardMeta = UserInfoActivityGoRouter.getCardMeta()

// 經(jīng)典:獲取User服務(wù)
UserService service = GoRouter.getInstance().getService(UserService.class);
// helper:獲取User服務(wù)
UserService service = UserServiceGoRouter.get()

// 經(jīng)典:獲取帶別名的服務(wù)
PayService service = GoRouter.getInstance().getService(PayService.class, "Alipay");
// helper:獲取帶別名的服務(wù)
PayService service = PayServiceForAlipayGoRouter.get();

// 經(jīng)典:向/main/activity發(fā)送事件
GoRouter.getInstance().postEvent("/main/activity", "Go!");
// helper:向/main/activity發(fā)送事件
MainActivityGoRouter.postEvent("Go!");

// helper:獲取path
String path = MainActivityGoRouter.getPath()

// 也可以通過helper類get/set其他屬性
UserSignInActivityGoRouter.build()
        .withAction(...)
        .withFlags(...)
        .go(this, ...);
  • @Route(deprecated = true)和打開了openDebug()的情況下窿克,框架跳轉(zhuǎn)到該頁將Toast提示其他開發(fā)人員和測(cè)試人員棕硫,并且生成的幫助類也會(huì)被自動(dòng)標(biāo)記@Deprecated,代碼上也會(huì)提示過時(shí),提醒開發(fā)人員更新跳轉(zhuǎn)代碼髓涯,這在多人開發(fā)時(shí)會(huì)很有用。@Service不會(huì)有這個(gè)問題哈扮,service直接在暴漏的服務(wù)接口上標(biāo)記@Deprecated纬纪,其他模塊調(diào)用者就能看到過時(shí)標(biāo)記。
  • @Route(ignoreHelper = true)的情況下滑肉,框架不會(huì)為這個(gè)頁面生成幫助類包各,適用于僅本模塊調(diào)用的頁面。
3. 設(shè)計(jì)思路

此功能所生成的幫助類會(huì)存放到com.wyjson.router.helper包下靶庙,按照模塊.路由分組.幫助類.java分別存放问畅,請(qǐng)勿手動(dòng)更改這些類。
最早設(shè)計(jì)的是整個(gè)項(xiàng)目生成一個(gè)GoRouterHelper.java類六荒,調(diào)用上比現(xiàn)在的每個(gè)頁面生成一個(gè)對(duì)應(yīng)的幫助類會(huì)更統(tǒng)一护姆,
但是這樣會(huì)導(dǎo)致多人開發(fā)每個(gè)人生成的GoRouterHelper.java類代碼合并沖突,所以想到把GoRouterHelper.java存放到根項(xiàng)目build目錄掏击,
存放到build目錄確實(shí)可以解決代碼沖突問題卵皂,但是新的問題也來了,在首次開啟生成幫助類的功能砚亭,項(xiàng)目里還沒有調(diào)用這個(gè)GoRouterHelper.java類的時(shí)候是不會(huì)有問題的灯变,
如果項(xiàng)目里已經(jīng)使用了這個(gè)幫助類,build目錄被清除或者重新clone代碼捅膘,此時(shí)項(xiàng)目就沒法運(yùn)行了添祸,因?yàn)槿鄙龠@個(gè)幫助類。
這里不得不說一下幫助類生成流程和AndroidbuildConfig任務(wù)的執(zhí)行順序寻仗,正常項(xiàng)目clone下來刃泌,idea提示報(bào)錯(cuò)缺少buildConfig.java類,
你去build項(xiàng)目署尤,Gradle會(huì)執(zhí)行buildConfig任務(wù)蔬咬,生成缺少的buildConfig.java類,再去生成common模塊aar沐寺。
路由幫助類在根項(xiàng)目里還不知道上層其他模塊里有什么頁面和服務(wù)的時(shí)候林艘,它的執(zhí)行順序是,先build出來common模塊混坞,在去build其他頁面業(yè)務(wù)模塊狐援,
最后在app項(xiàng)目匯總钢坦,此時(shí)知道了所有頁面和服務(wù)去生成幫助類到根模塊項(xiàng)目。說回剛才的話題啥酱,項(xiàng)目里使用了幫助類爹凹,而幫助類又存放到build里,
此時(shí)要是缺少了build目錄镶殷,會(huì)導(dǎo)致項(xiàng)目無法運(yùn)行禾酱,其實(shí)有其他方案解決這個(gè)問題,但都不是很好绘趋,
所以最后我把這些幫助類存放到了根項(xiàng)目/src/main/java/com/wyjson/router/helper/...目錄下颤陶,按照頁面和服務(wù)生成對(duì)應(yīng)的幫助類,
解決了多人開發(fā)代碼沖突的問題陷遮。雖然這些類不在build目錄里滓走,也不用擔(dān)心無用的類,框架在每次生成新的代碼的時(shí)候會(huì)自動(dòng)刪除無用的類帽馋,
build目錄機(jī)制一樣搅方。如果其他業(yè)務(wù)模塊你不是通過本地引用,而是通過aar等方式引入绽族,那框架只會(huì)更新本地引用模塊項(xiàng)目的幫助類目錄姨涡,
不會(huì)更改和刪除aar模塊在根項(xiàng)目里的幫助類,這樣做到了你所開發(fā)模塊幫助類的更新而不影響其他團(tuán)隊(duì)生成的幫助類吧慢。

八涛漂、其他

1. Kotlin項(xiàng)目中的配置方式
plugins {
    ...
    id 'kotlin-kapt'
}

kapt {
    arguments {
        arg("GOROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    kapt 'com.github.wyjsonGo.GoRouter:GoRouter-Compiler:2.5.2'
}

module_kotlin模塊Demo示例module_kotlin/build.gradle

2. Kotlin類中的字段無法注入如何解決?

首先娄蔼,Kotlin中的字段是可以自動(dòng)注入的,但是注入代碼為了不使用反射底哗,使用的字段賦值的方式來注入的岁诉,Kotlin默認(rèn)會(huì)生成set/get方法,并把屬性設(shè)置為private 所以只要保證Kotlin中字段可見性不是private即可跋选,簡(jiǎn)單解決可以在字段上添加 @JvmField涕癣。Demo示例KotlinActivity.kt

3. 路由中的分組概念
  • 路由路徑支持(a-zA-Z0-9_-.#)
  • SDK中針對(duì)所有的路徑/test/1/test/2進(jìn)行分組前标,分組只有在分組中的某一個(gè)路徑第一次被訪問的時(shí)候坠韩,該分組才會(huì)被初始化。分組使用路徑中第一段字符串(/*/)作為分組炼列,這里的路徑需要注意的是至少需要有兩級(jí)/xx/xx只搁。
  • GRouter允許一個(gè)module中存在多個(gè)分組,也允許多個(gè)module中存在相同的分組俭尖,但是最好不要在多個(gè)module中存在相同的分組氢惋,因?yàn)樵谧?cè)路由組時(shí)發(fā)現(xiàn)存在相同的分組洞翩,會(huì)立即注冊(cè)老的路由組里的全部路由,然后更新新的路由組信息焰望。
4. 攔截器和服務(wù)的異同
  • 攔截器和服務(wù)所需要實(shí)現(xiàn)的接口不同骚亿,但是結(jié)構(gòu)類似,都存在init()方法熊赖,但是兩者的調(diào)用時(shí)機(jī)不同来屠。
  • 攔截器因?yàn)槠涮厥庑裕粚?duì)Activity路由有效震鹉,攔截器會(huì)在GoRouter首次調(diào)用的時(shí)候初始化俱笛。
  • 服務(wù)沒有該限制,某一服務(wù)可能在App整個(gè)生命周期中都不會(huì)用到足陨,所以服務(wù)只有被調(diào)用的時(shí)候才會(huì)觸發(fā)初始化操作嫂粟。
5. 使用java方式注冊(cè)服務(wù)
  • 實(shí)現(xiàn)相同服務(wù)(HelloService)的實(shí)現(xiàn)類(HelloServiceImpl)多次調(diào)用addService()方法會(huì)被覆蓋(更新),全局唯一墨缘,使用getService(service)方法獲取星虹。
  • 實(shí)現(xiàn)相同服務(wù)(PayService)的多個(gè)實(shí)現(xiàn)類(AliPayServiceImpl、WechatPayServiceImpl)調(diào)用addService(service, alias)方法注冊(cè)镊讼,使用getService(service, alias)方法獲取宽涌。
6. 使用java方式注冊(cè)攔截器
  • addInterceptor(ordinal, interceptor)重復(fù)添加相同序號(hào)會(huì)catch。
  • setInterceptor(ordinal, interceptor)重復(fù)添加相同序號(hào)會(huì)覆蓋(更新)蝶棋。
7. 混淆

框架已經(jīng)做了混淆處理卸亮,開發(fā)者無需關(guān)心。

# 如果使用了 GoRouter.getInstance().inject(this) 方法玩裙,需添加下面規(guī)則兼贸,保護(hù)字段
-keepclassmembers class * {
    @com.wyjson.router.annotation.Param <fields>;
}
8. inject()工作原理
  • 2.3.2版本之前,GoRouter.getInstance().inject(this)方法會(huì)先通過this參數(shù)拿到bundle對(duì)象吃溅,再去獲取當(dāng)前頁面的path溶诞,通過path拿到CardMeta數(shù)據(jù),利用java反射進(jìn)行數(shù)據(jù)的綁定决侈。
  • 2.3.2版本起螺垢,使用@Param注解會(huì)自動(dòng)生成參數(shù)注入類,內(nèi)部代碼是原生寫法赖歌,性能更好枉圃,使用方法參見4-2。
9. go()無參方法

如果你沒有使用自動(dòng)加載路由表方法GoRouter.autoLoadRouteModule(this)庐冯,也沒有使用多模塊applicationGoRouter.callAMOnCreate(this)方法孽亲,此時(shí)你去使用go()無參方法需要在application里調(diào)用GoRouter.setApplication(...)設(shè)置一個(gè)上下文。

10. 重寫跳轉(zhuǎn)URL服務(wù)

本庫刪除了PathReplaceService展父,可以在IPretreatmentService里實(shí)現(xiàn)相同功能墨林。使用card.setPath(...)card.setUri(...)方法赁酝,效果一樣。

11. 開啟調(diào)試旭等,查看日志可以檢查使用java方式注冊(cè)的路由是否有重復(fù)提交的情況
route path[/xx/xx] duplicate commit!!!
route pathClass[class xx.xx] duplicate commit!!!

GoRouter日志tag為GoRouter酌呆,GoRouter-Compiler日志tag為GoRouter::Compiler,GoRouter-Gradle-Plugin日志tag為GoRouter::Gradle-Plugin搔耕。

12. ARouter遷移指南
ARouter GoRouter
ARouter GoRouter
init(app) autoLoadRouteModule(app)
navigation() go()
NavigationCallback GoCallback
IProvider IService
DegradeService IDegradeService
PretreatmentService IPretreatmentService
SerializationService IJsonService
Postcard Card
@Route @Route
@Route @Service
@Route @Interceptor
@Autowired @Param

Github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末隙袁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子弃榨,更是在濱河造成了極大的恐慌菩收,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲸睛,死亡現(xiàn)場(chǎng)離奇詭異娜饵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)官辈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門箱舞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拳亿,你說我怎么就攤上這事晴股。” “怎么了肺魁?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵电湘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鹅经,道長(zhǎng)寂呛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任瘾晃,我火速辦了婚禮贷痪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酗捌。我一直安慰自己呢诬,他們只是感情好涌哲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布胖缤。 她就那樣靜靜地躺著,像睡著了一般阀圾。 火紅的嫁衣襯著肌膚如雪哪廓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天初烘,我揣著相機(jī)與錄音涡真,去河邊找鬼分俯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哆料,可吹牛的內(nèi)容都是我干的缸剪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼东亦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼杏节!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起典阵,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤奋渔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后壮啊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫉鲸,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年歹啼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玄渗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡染突,死狀恐怖捻爷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情份企,我是刑警寧澤也榄,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站司志,受9級(jí)特大地震影響甜紫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骂远,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一囚霸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧激才,春花似錦拓型、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至东帅,卻和暖如春压固,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背靠闭。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工帐我, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坎炼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓拦键,卻偏偏與公主長(zhǎng)得像谣光,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芬为,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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