手把手带你搭建Mvp+Dagger架构

0. 序言

  • 之前写过一篇名为"看完不会写MVP架构我跪搓板"的博文,收到一些阅读者的建议,希望能够对Rxjava的生命周期进行管理以及添加Dagger到MVP架构中,所以今天抽一点时间写一篇拿来即可于实战的Demo。假如对基本的MVP架构不甚了解的可先看上述博文,因为这篇博文重点讲解Dagger部分,博文地址:https://www.jianshu.com/p/44399bf0c8d2 (有些地方还是讲得不够清楚,有些地方没有讲,会对Dagger方面的文章进行再次梳理)
  • Demo的技术组成:Mvp+Retrofit2+Rxjava2+RxLifeCycle2+Dagger2,其中RxLifeCycle2即为管理Rxjava生命周期的三方库。
  • 有些类代码较多,只拿核心代码呈现,完整的项目地址见文尾。

1. 博文目录

  • 添加依赖
  • 创建项目基本目录
  • 实现Model
  • 定义契约接口NewsInfoContract
  • 实现Presenter
  • 实现View
  • 补充网络配置代码
  • 适配Android28网络请求
  • 添加Dagger2
  • 实现RetrofitManager单例

2. 添加依赖

    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.4'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle:2.2.1'

3. 创建项目基本目录

image.png

4. 实现Model

  • 创建实体类NewsInfo:
public class NewsInfo {
    private String reason;
    private ResultBean result;
    ...
    public static class ResultBean {
        private String stat;
        private List<DataBean> data;
        ...
        public static class DataBean {
            private String uniquekey;
            private String title;
            private String date;
            private String category;
            private String author_name;
            private String url;
            private String thumbnail_pic_s;
            private String thumbnail_pic_s02;
            private String thumbnail_pic_s03;
            ...
        }
    }
}
  • 定义获取网络数据的接口类NetTask:
public interface NetTask {
    void execute(LifecycleProvider lifecycleProvider, String type, LoadTasksCallBack callBack);
}
public interface LoadTasksCallBack {
    void OnSuccess(NewsInfo newsInfo);
    void OnStart();
    void onFailed();
    void onFinish();
}
  • 编写NetTask的实现类NewsInfoTask:
public class NewsInfoTask implements NetTask {

    private Disposable mDisposable;

    private NewsInfoTask() {

    }

    public static NewsInfoTask getInstance() {
        return NewsInfoTaskHolder.sNewsInfoFask;
    }

    private static class NewsInfoTaskHolder {
        private static final NewsInfoTask sNewsInfoFask = new NewsInfoTask();
    }

    @Override
    public void execute(LifecycleProvider lifecycleProvider,String type, final LoadTasksCallBack callBack) {

        RetrofitManager.getInstance().getRetrofit(Constant.BASEURL).create(NewsService.class)
                .getNewsInfo(type, BuildConfig.NewKey)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .compose(lifecycleProvider.<Long>bindUntilEvent(Lifecycle.Event.ON_DESTROY))
                .subscribe(new Observer<NewsInfo>() {
                    @Override
                    public void onSubscribe(Disposable disposable) {
                        mDisposable= disposable;
                        callBack.OnStart();
                    }

                    @Override
                    public void onNext(NewsInfo newsInfo) {
                        callBack.OnSuccess(newsInfo);
                    }

                    @Override
                    public void onError(Throwable e) {
                        callBack.onFailed();
                    }

                    @Override
                    public void onComplete() {
                        callBack.onFinish();
                        mDisposable.dispose();
                    }
                });
    }
}

6. 定义契约接口NewsInfoContract:

public interface NewsInfoContract {
    interface Presenter{
        void getNewsInfo(LifecycleProvider lifecycleProvider, String type);
    }

    interface View {
        void setNewsInfo(NewsInfo newsInfo);
        void showLoading();
        void hideLoading();
        void showError();
    }
}

7. 实现Presenter

  • 编写NewsInfoContract.Presenter接口的实现类NewsInfoPresenter:
public class NewsInfoPresenter implements NewsInfoContract.Presenter,LoadTasksCallBack {
    private NetTask mNetTask;
    private NewsInfoContract.View mView;

    public NewsInfoPresenter(NetTask netTask,NewsInfoContract.View view) {
        mNetTask = netTask;
        mView = view;
    }

        @Override
    public void getNewsInfo(LifecycleProvider lifecycleProvider, String type) {
        mNetTask.execute(lifecycleProvider,type,this);
    }

    @Override
    public void OnSuccess(NewsInfo newsInfo) {
        mView.setNewsInfo(newsInfo);
    }

    @Override
    public void OnStart() {
        mView.showLoading();
    }

    @Override
    public void onFailed() {
        mView.showError();
        mView.hideLoading();
    }

    @Override
    public void onFinish() {
        mView.hideLoading();
    }
}  

6. 实现View

public class MainActivity extends AppCompatActivity implements NewsInfoContract.View {

    private NewsInfoContract.Presenter mPresenter = new NewsInfoPresenter(NewsInfoTask.getInstance(),this);
    LifecycleProvider<Lifecycle.Event> lifecycleProvider = AndroidLifecycle.createLifecycleProvider(this);
    private TextView mNew_Content;
    private Dialog mDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mNew_Content = findViewById(R.id.tv_content);
        mDialog = new ProgressDialog(this);
        mDialog.setTitle(R.string.dialog_get_info);
        findViewById(R.id.bt_get_news).setOnClickListener(v -> {
            mPresenter.getNewsInfo(lifecycleProvider,Constant.DEFAULT_TYPE);
        });
    }

    @Override
    public void setNewsInfo(NewsInfo newsInfo) {
        if (newsInfo != null && newsInfo.getResult() != null && newsInfo.getResult().getData() != null) {
            mNew_Content.setText(newsInfo.getResult().getData().get(0).getTitle());
        }
    }

    @Override
    public void showLoading() {
        mDialog.show();
    }

    @Override
    public void hideLoading() {
        if (mDialog.isShowing())
            mDialog.dismiss();
    }

    @Override
    public void showError() {
        Toast.makeText(this, R.string.toast_net_tip, Toast.LENGTH_SHORT).show();
    }

}

7. 补充网络配置代码

  • Retrofit 管理类:
public class RetrofitManager {

    private static Retrofit sRetrofit = null;
    private static String sUrl = "";
    private static final int TIMEOUT = 20;

    private RetrofitManager() {
    }

    public static RetrofitManager getInstance() {
        return RetrofitManagerHolder.sInstance;
    }

    private static class RetrofitManagerHolder {
        private static final RetrofitManager sInstance = new RetrofitManager();
    }

    public Retrofit getRetrofit(String baseUrl) {
        sUrl = baseUrl;
        if (sRetrofit == null) {
            return create();
        } else {
            return sRetrofit;
        }
    }

    private Retrofit create() {

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG) {
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        } else {
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        }

        OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(loggingInterceptor)
                .connectTimeout(TIMEOUT,TimeUnit.SECONDS)
                .readTimeout(TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true).build();

        sRetrofit = new Retrofit.Builder()
                .baseUrl(sUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build();
        return sRetrofit;
    }
}
  • 网络请求接口服务类NewsService:
public interface NewsService {

    @GET("toutiao/index")
    Observable<NewsInfo> getNewsInfo(@Query("type") String type, @Query("key")String key);
}

8. 适配Android28网络请求

  • 在res目录下创建名为xml的文件夹,并在文件夹里面创建名为network_security_config的xml文件:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  • AndroidManifest配置文件中添加配置:
android:networkSecurityConfig="@xml/network_security_config"

9. 添加Dagger2

9.0 分析

这里我们需要把NewsInfoTask和MainActivity注入到了Presenter中,把Presenter注入到了MainActivity中。

9.1 Dagger2实现NewsInfoTask单例
  • 去掉NewsInfoTask以下代码:
    private NewsInfoTask() {

    }

    public static NewsInfoTask getInstance() {
        return NewsInfoTaskHolder.sNewsInfoFask;
    }

    private static class NewsInfoTaskHolder {
        private static final NewsInfoTask sNewsInfoFask = new NewsInfoTask();
    }
  • 新建名为NetTaskModule的Module:
@Module
public class NetTaskModule {
    @Singleton
    @Provides
    public NetTask provideNewsInfoTask(){
        return new NewsInfoTask();
    }
}

说明:生成NewsInfoTask的实例对象,并用@Singleton修饰。

  • 新建名为NetTaskComponent的Component:
@Singleton
@Component(modules = NetTaskModule.class)
public interface NetTaskComponent {
    NetTask getNetTask();
}

说明:
① NetTaskModule 中的方法provideNewsInfoTask用@Singleton修饰,NetTaskComponent也必须用@Singleton修饰。
② NetTaskComponent中不需要指明注入的目标,而需要提供实例对象的时候,可以用“NetTask getNetTask();”这种形式表示,指明返回的是NetTask,返回值很重要。

  • 实现NewsInfoTask在App全局单例:
    @Singleton可以保证局部单例,即NetTaskComponent下的注入目标中NewsInfoTask的内存地址都是同一个,而一旦创建其他Component并关联NetTaskModule,此时创建出的NewsInfoTask的内存地址就会发生变化,所以保证全局单例我们只能初始化一次Component,而初始化的地方就是Application:
public class App extends Application {

    private NetTaskComponent mNetTaskComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mNetTaskComponent = DaggerNetTaskComponent.builder().build();
    }

    public static App get(Context context){
        return (App)context.getApplicationContext();
    }

    public NetTaskComponent getNetTaskComponent() {
        return mNetTaskComponent;
    }
}

说明:这里通过.builder().build()来获取NetTaskComponent。需要NewsInfoTask只通过NetTaskComponent,就可以保证NewsInfoTask对象的内存地址唯一了。

9.2 Presenter注入MainActivity
  • @Inject修饰构造方法
    @Inject
    public NewsInfoPresenter(NewsInfoContract.View view,NetTask netTask) {
        mView = view;
        mNetTask = netTask;
    }

说明:@Inject修饰构造方法意思是告诉Dagger2可以用这个构造方法构建NewsInfoPresenter。只有构造方法上有@Inject注解修饰,NewsInfoPresenter才可以对外提供实例对象。

  • NewsInfoContract.View添加setPresenter方法
public interface NewsInfoContract {
    interface Presenter{
        void getNewsInfo(LifecycleProvider lifecycleProvider, String type);
    }

    interface View {
        void setNewsInfo(NewsInfo newsInfo);
        void showLoading();
        void hideLoading();
        void showError();
        void setPresenter(NewsInfoPresenter presenter);
    }
}
  • NewsInfoPresenter创建setPresenter方法,并用@Inject修饰
    @Inject
    void setPresenter(){
        mView.setPresenter(this);
    }

说明:@Inject修饰方法的意思是方法注入,这里是把NewsInfoPresenter注入给MainActivity。方法注入是在构造方法后执行的。方法注入需要构造方法使用@Inject注解修饰,不然方法注入无法执行。

  • MainActivity中用@Inject修饰变量NewsInfoPresenter:
    @Inject
    NewsInfoPresenter mPresenter;

说明:@Inject修饰变量意思是NewsInfoPresenter需要依赖注入。

  • MainActivity实现setPresenter方法
    @Override
    public void setPresenter(NewsInfoPresenter presenter) {
        mPresenter = presenter;
    }

综上:以上几步完成了Persenter注入到MainActivity。

9.3 NewsInfoTask和MainActivity注入到了Presenter

  • 创建名为ActivityScoped的自定义Scope:
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScoped {
}
  • 创建NewsInfoModule的Module:
@Module
public class NewsInfoModule {

    private NewsInfoContract.View mView;

    public NewsInfoModule(NewsInfoContract.View view) {
        mView = view;
    }

    @Provides
    public NewsInfoContract.View provideNewsInfoContractView() {
        return mView;
    }

}

说明:这里是为了将NewsInfoContract.View注入给MainActivity。

  • 创建名为MainActivityComponent的Component:
@ActivityScoped
@Component(modules = NewsInfoModule.class,dependencies =NetTaskComponent.class)
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

说明:
① dependencies意思是把NetTaskComponent的内容也拿过来注入。
② @ActivityScoped之所以创建自定义Scope是因为如果dependencies中的NetTaskComponent用了@Singleton修饰,这里就不能使用@Singleton修饰了。

  • 修改MainActivity完成注入:
DaggerMainActivityComponent.builder().newsInfoModule(new NewsInfoModule(this))
                .netTaskComponent(App.get(this).getNetTaskComponent()).build().inject(this);

说明:因为Presenter需要两个参数,所以这里一句话就把需要的两个参数传入了Presenter。我们看下DaggerMainActivityComponent的源码:

    public Builder newsInfoModule(NewsInfoModule newsInfoModule) {
      this.newsInfoModule = Preconditions.checkNotNull(newsInfoModule);
      return this;
    }

    public Builder netTaskComponent(NetTaskComponent netTaskComponent) {
      this.netTaskComponent = Preconditions.checkNotNull(netTaskComponent);
      return this;
    }

说明:.newsInfoModule和.netTaskComponent接收Presenter需要的两个参数。然后看build():

public MainActivityComponent build() {
      if (newsInfoModule == null) {
        throw new IllegalStateException(NewsInfoModule.class.getCanonicalName() + " must be set");
      }
      if (netTaskComponent == null) {
        throw new IllegalStateException(NetTaskComponent.class.getCanonicalName() + " must be set");
      }
      return new DaggerMainActivityComponent(this);
    }

说明:这里把build()所在的Build传入了DaggerMainActivityComponent的构造方法,我们看下:

  private DaggerMainActivityComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

说明:再看下initialize方法:

this.provideNewsInfoContractViewProvider =
        NewsInfoModule_ProvideNewsInfoContractViewFactory.create(builder.newsInfoModule);

    this.getNetTaskProvider =
        new Factory<NetTask>() {
          private final NetTaskComponent netTaskComponent = builder.netTaskComponent;

          @Override
          public NetTask get() {
            return Preconditions.checkNotNull(
                netTaskComponent.getNetTask(),
                "Cannot return null from a non-@Nullable component method");
          }
        };

    this.newsInfoPresenterProvider =
        NewsInfoPresenter_Factory.create(
            newsInfoPresenterMembersInjector,
            provideNewsInfoContractViewProvider,
            getNetTaskProvider);

说明:newsInfoModule得到provideNewsInfoContractViewProvider,netTaskComponent得到getNetTaskProvider,然后把两者,放入NewsInfoPresenter_Factory.create方法中:

  public static Factory<NewsInfoPresenter> create(
      MembersInjector<NewsInfoPresenter> newsInfoPresenterMembersInjector,
      Provider<NewsInfoContract.View> viewProvider,
      Provider<NetTask> netTaskProvider) {
    return new NewsInfoPresenter_Factory(
        newsInfoPresenterMembersInjector, viewProvider, netTaskProvider);
  }

说明:再看NewsInfoPresenter_Factory

  public NewsInfoPresenter_Factory(
      MembersInjector<NewsInfoPresenter> newsInfoPresenterMembersInjector,
      Provider<NewsInfoContract.View> viewProvider,
      Provider<NetTask> netTaskProvider) {
    assert newsInfoPresenterMembersInjector != null;
    this.newsInfoPresenterMembersInjector = newsInfoPresenterMembersInjector;
    assert viewProvider != null;
    this.viewProvider = viewProvider;
    assert netTaskProvider != null;
    this.netTaskProvider = netTaskProvider;
  }

说明:用viewProvider封装了NewsInfoContract.View;用netTaskProvider封装了NetTask。他们调用给了get方法:

  @Override
  public NewsInfoPresenter get() {
    return MembersInjectors.injectMembers(
        newsInfoPresenterMembersInjector,
        new NewsInfoPresenter(viewProvider.get(), netTaskProvider.get()));
  }

说明:看到这里,你会清晰的看到NetTask和MainActivity注入到了Presenter,而这个get方法何时调用的呢?我们看下inject方法:

  @Override
  public void inject(MainActivity mainActivity) {
    mainActivityMembersInjector.injectMembers(mainActivity);
  }

说明:再接着看injectMembers方法:

    @Override
    public void injectMembers(MainActivity instance) {
      if (instance == null) {
        throw new NullPointerException("Cannot inject members into a null reference");
      }
      instance.mPresenter = mPresenterProvider.get();
    }

说明:mPresenterProvider是谁呢:

private final Provider<NewsInfoPresenter> mPresenterProvider;

说明:到这里会发现其实inject的目的就是把两个参数赋值给this中的presenter,而赋值离不开new,自然离不开构造方法,而构造方法那里也离不开@Inject。而这也正是Dagger2的原理所在,通过工厂方法把实例对象赋值给注入目标的用@Inject所修饰的成员变量。

10. 实现RetrofitManager单例

  • 去除之前实现单例模式的以下代码
    private RetrofitManager() {
    }

    public static RetrofitManager getInstance() {
        return RetrofitManagerHolder.sInstance;
    }

    private static class RetrofitManagerHolder {
        private static final RetrofitManager sInstance = new RetrofitManager();
    }
  • NetTaskModule中添加以下方法:
    @Singleton
    @Provides
    public RetrofitManager provideRetrofitManager(){
        return new RetrofitManager();
    }
  • NetTaskComponent中添加以下方法:
@Singleton
@Component(modules = NetTaskModule.class)
public interface NetTaskComponent {
    NetTask getNetTask();
    RetrofitManager getRetrofitManager();
}
  • 修改Applicaion的代码:
public class App extends Application {

    private static NetTaskComponent mNetTaskComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mNetTaskComponent = DaggerNetTaskComponent.builder().build();
    }

    public static NetTaskComponent getNetTaskComponent() {
        return mNetTaskComponent;
    }
}

说明:之前的方式需要传入Context,但是NewsInfoTask中并没有Context,所以我们这里修改为static,因为是单例模式,从创建就开始存在直到App应用程序退出,所以不会有内存泄露的情况。

  • MainActivity和NewsInfoTask中修改获取NetTaskComponent的代码。

11. 代码下载地址:

https://github.com/OnlyYouMyLove/Mvp_Dagger2
这篇文章的Model和View是通过Build参数的方式通过Presenter的构造方法注入;还可以Model单独注入到Presenter,View通过Build参数的方式注入到Presenter,这里不再给出代码,感兴趣的可以留言。

12. 后续

如果大家喜欢这篇文章,欢迎点赞;如果想看更多 Dagger 方面的技术,欢迎关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
    SkyMing一C阅读 1,612评论 0 22
  • 糊汤面,顾名思义这碗饭离不开糊汤,面条。 那么就来告诉你关于它的一切,一切你知道的和不知道的。 第一步 食材 五花...
    星云_fighting阅读 1,587评论 14 15
  • 霍凌晟揉了揉酸胀的眉间,另一只手拿过资料,认真地翻阅,翻到一张照片忽然停下,怔怔地望着。他怀疑老天绝对是跟他有仇,...
    暖暖加糖阅读 1,088评论 10 11
  • 站在世界的这头,我确定你一定在世界的那头只是不知道会以怎样的方式相遇 风吹雨打,日出日落依然活的凋零温柔在身体里起...
    嘉温阅读 429评论 7 24