Live-client-2-RxJava、Retrofit、Room与MVP架构

在Android项目中,采用了MVP的架构,MVP架构主要是为了解决以往MVC架构下,在Activity中处理业务逻辑导致的耦合性强的问题。MVP架构的介绍。在上一篇文章(Live-client-1-UI界面的设计)中,已经完成大部分的UI界面的设计,再编写对应的Activity和fragment就大致完成了MVP中的View视图层,接下来要做的是Model层和Presenter层的设计和实现。

在项目中选用了Room,这个Google官方的数据库框架,也是Jetpack架构组件库中的一员,通常和Databinding、LiveData、ViewModel框架使用,实现MVVM架构。但是在本项目中并没有这么做, 而是将Room替代以往复杂的Sqlite操作,并利用Room数据库框架提供可以直接返回RxJava的Flowable可观察对象的特性,来实现数据库的访问,并返回Flowable可观察对象提供给Rxjava进行处理。

既然提及到了Rxjava,那么Rxjava又是什么呢?Rxjava是一个基于事件、通过使用观察者序列来编写异步代码的库。Rxjava将链式编程风格和异步结合在一起,并基于观察者模式。在Android中使用Rxjava,能快速实现线程切换、异步回调、消息处理等日常编码中出现的难题,同时使得代码逻辑更加清晰明了。

Retrofit的官方描述

A type-safe HTTP client for Android and Java

一种适用于Android和Java的类型安全的HTTP客户端。通过官方的介绍,可以得知Retrofit也是支持链式调用,同时也支持将请求的结果转为Flowable可观察对象,因此通过Retrofit网络框架请求得来的网络数据也可以通过Rxjava来进行处理。

于是,我们就可以通过Rxjava来处理本地数据库和网络请求得到的数据,供Presenter层进行处理。

Model层

Model层要做的东西其实还挺多的:

  1. 首先要定义实体类;
  2. 根据实体类创建Room数据库框架所需要的Dao、Database,完成数据库的定义和创建;
  3. 创建数据接口,实现本地、网络数据接口,最后创建数据仓库Repository类来组合本地数据和网络数据。
mvp-1.png

但是在这个项目中,我将网络请求部分从Repository中分离,直接提供接口给Presenter,这样做会加重Presenter层的负担,但是在逻辑上更加清晰,明确需要从网络中请求时就直接调用retrofit的接口,而不是向以往一样从repository中调用网络数据接口发起retrofit的请求。尽管逻辑清晰了, 但是会使得Model和Presenter的耦合性增加,因此不太推荐这种写法,在后期可能会对该部分进行重构。


mvp-2.png

Model目录如下:


Model目录.png

实体类

定义一个User实体类,包含用户名、账号、密码三个属性,同时作为Room框架中的实体,要添加@Entity注解。

**
 * 用户表
 * @author Ljh 2019/7/10 15:49
 */
@Entity
public class User {

    private String name;

    @NonNull
    @PrimaryKey
    private String account;

    @NonNull
    private String password;

    public User() {
    }

    @Ignore
    public User(String name, @NonNull String phone, @NonNull String password) {
        this.name = name;
        this.account = phone;
        this.password = password;
    }

   // ...  get/set方法省略
}

Dao

UserDao编写数据访问的相关方法及返回值,要添加@Dao注解将类标记为数据访问对象。代码示例

/**
 * @author Ljh 2019/7/10 16:26
 */
@Dao
public interface UserDao {

    /**
     * 按照主键phone查询User,通过rxjava的single
     *
     * @param account 用户账号:手机/email
     * @return 返回单个user,如果找不到onError(EmptyResultSetException.class)
     */
    @Query("SELECT * FROM user where account =:account")
    Single<User> getUserByPhone(String account);

    /**
     * 添加用户
     *
     * @param user 用户
     * @return 返回
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    Completable insertUser(User user);

    /**
     * 更新用户
     *
     * @param user 用户信息
     * @return 返回成功与否
     */
    @Update(onConflict = OnConflictStrategy.REPLACE)
    Completable updateUser(User user);

    /**
     * 删除用户
     *
     * @param account 用户账号 手机/email
     * @return 返回成功与否
     */
    @Query("DELETE FROM user where account = :account")
    Completable deleteUser(String account);
}

在最初接触Room框架时,Room仅仅支持对@Query查询出来的数据提供Flowable可观察对象,在Version 2.1.0-alpha01时,就添加对@Insert、@Update、@Delete等操作结果的观察,也就是说在Version 2.1.0-alpha01后的版本的Room配合Rxjava使用才不会那么别扭,可以观察所有的数据库操作,然后放入Rxjava中操作。


Room-Version 2.1.0-alpha01.png

数据库创建

创建数据库时要使用单例模式,避免创建多个数据库实例,导致内存泄漏。需要使用@Database()注解配置数据实体类、数据库版本等信息。

/**
 * @author Ljh 2019/7/10 17:42
 */
@Database(entities = {User.class, LiveInfo.class}, version = 1, exportSchema = false)
public abstract class LiveDataBase extends RoomDatabase {
    public static final String TAG = LiveDataBase.class.getSimpleName();
    public static LiveDataBase INSTANCE;

    public abstract UserDao userDao();

    public abstract LiveInfoDao liveInfoDao();

    private static final Object sLock = new Object();

    public static LiveDataBase getInstance() {
        synchronized (sLock) {
            if (INSTANCE == null) {
                Log.d(TAG, "Create Database");
                INSTANCE = Room.databaseBuilder(MyApplication.getMyApplicationContext(),
                        LiveDataBase.class, "Live.db")
                        .build();
            }
            return INSTANCE;
        }
    }
}

完成以上步骤之后,就完成了实体类、数据表、数据库和数据库CRUD的接口。接下来要看数据接口怎么设计。

数据接口与数据仓库Repository

编写数据接口的目的是为了在数据库CRUD接口之上再次进行封装、处理,然后提供给Presenter层使用,其本质还是CRUD等操作。UserDataSource如下:

public interface UserDataSource {

    Single<User> getUserByPhone(String account);

    Completable insertUser(User user);

    Completable updateUser(User user);

    Completable deleteUser(String account);
}

本地数据源UserLocalDataSource:

public class UserLocalDataSource implements UserDataSource {

    private static UserLocalDataSource INSTANCE;

    private UserDao mUserDao;


    public UserLocalDataSource(UserDao userDao) {
        this.mUserDao = userDao;
    }

    public static UserLocalDataSource getInstance(UserDao userDao) {
        if (INSTANCE == null) {
            INSTANCE = new UserLocalDataSource(userDao);
        }
        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }

    @Override
    public Single<User> getUserByPhone(String account) {
        return mUserDao.getUserByPhone(account)
                .subscribeOn(Schedulers.io());
    }

    @Override
    public Completable insertUser(User user) {
        return mUserDao.insertUser(user)
                .subscribeOn(Schedulers.io());
    }

    @Override
    public Completable updateUser(User user) {
        return mUserDao.updateUser(user)
                .subscribeOn(Schedulers.io());
    }

    @Override
    public Completable deleteUser(String account) {
        return mUserDao.deleteUser(account)
                .subscribeOn(Schedulers.io());
    }
}

UserRepository数据仓库:

/**
 * @author Ljh 2019/7/15 10:04
 */
public class UserRepository implements UserDataSource {

    private static UserRepository INSTANCE;

    private final UserLocalDataSource mUserLocalDataSource;

    private UserRepository() {
        this.mUserLocalDataSource = UserLocalDataSource.getInstance(LiveDataBase.getInstance().userDao());
    }

    public static UserRepository getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new UserRepository();
        }
        return INSTANCE;
    }

    @Override
    public Single<User> getUserByPhone(String account) {
        return mUserLocalDataSource.getUserByPhone(account);
    }

    @Override
    public Completable insertUser(User user) {
        return mUserLocalDataSource.insertUser(user);
    }

    @Override
    public Completable updateUser(User user) {
        return mUserLocalDataSource.updateUser(user);
    }

    @Override
    public Completable deleteUser(String account) {
        return mUserLocalDataSource.deleteUser(account);
    }
}

可以看到,所有的操作只是对数据库Dao接口的调用,再封装一个数据仓库的单例模式。如果这个简单的应用没有后续的扩展和改善的话,那么像数据仓库中不处理网络请求的写法,其实有很多部分都是冗余的,没有必要的。因为UserRepository中只是简单的调用了UserLocalDataSource,而UserLocalDataSource又只是简单的调用了数据库Dao的接口。如果添加一个UserRemoteDataSource专门用来处理网络请求及其响应数据,然后在UserRepository中进行本地、网络的逻辑处理,那么这样的Model层设计还算有点用处。

View层与Presenter层

View层和Presenter层的关联比较紧密,这里就一同讲述。每一个View和Presenter都有一些相同的方法,我们将该部分方法抽出,分离成接口,让每个View和Presenter来实现。

BaseView

在BaseView这个接口中,需要将每个Presenter和当前view进行绑定,也需要在每个View中显示Toast,于是BaseView的设计如下:

public interface BaseView<T> {
    void setPresenter(T presenter);
    void showToast(String message);
}

通过一个模板类的设计,可以将view和不同的Presenter进行绑定。

BasePresenter

在BasePresenter中则较为简单,只是提供了一个start()方法,给每个Presenter进行一些初始化的操作。

public interface BasePresenter {
    void start();
}

Login功能模块:


login-功能模块.png

LoginContract

下面以登录这个功能来讲解MVP架构中,View和Presenter的实现。

Contract的意思是契约,通过LoginContract类,来详细定义View和Presenter中所需要的方法:

  1. 在Presenter方面:首先在登录注册功能中,肯定要有一个登录方法、一个注册方法来完成这两项功能,其次,在注册的时候,需要获取验证码进行注册,于是又要定义一个发送验证码的方法。
  2. 在View方面:由于要在一个页面中转换登录和注册框,因此需要一个显示登录框的方法、一个显示注册框的方法;由于背景使用了Bing的每日一图,于是又定义一个显示每日一图的方法;在请求验证码时,通常要避免用户不断请求验证码,导致验证码的时效发生变化和避免增加服务器的负担,需要设置一个倒计时方法。

这时,可能会有这样的疑问:为什么还要设置一个契约接口来定义上述的方法呢?其实目的是为了将View和Presenter的所有方法集中在一起,便于查看。同时,通过setPresenter()方法的实现,可以将view和presenter联系在一起,相互调用对方的方法,给对方提供调用接口,在逻辑上更加清晰。
代码示例:

public interface LoginContract {

    interface View extends BaseView<Presenter> {
        //显示Bing每日一图作为全局背景
        void showBingPicBg(String address);

        //显示注册框
        void showRegister();

        //显示登录框
        void showLogin();

        //对页面的返回进行设置
        void setResult(String result, String msg);

        //取消短信倒计时
        void cancelTimer();
    }

    interface Presenter extends BasePresenter {
        //使用账号密码登录
        void login(String phone, String password, String accountType);

        //通过user和验证码注册
        void register(User user, String code, String accountType);

        //发送验证码
        void sendMessage(String phone, String accountType);
    }
}

LoginFragment

创建一个实现了LoginContract.View接口的Fragment,并通过单例模式获取该Fragment,避免重复创建View带来的花销。
在Fragment的onCreateView方法中,通过LayoutInflater来查找和加载Fragment对应的ui布局,并给ui布局中的控件进行初始化。

public class LoginFragment extends Fragment implements LoginContract.View, View.OnClickListener {
    public static final String TAG = LoginFragment.class.getSimpleName();

    private LoginContract.Presenter mPresenter;
  
    //... 省略UI控件

    //验证码倒计时
    private CountDownTimer timer = new CountDownTimer(60 * 1000, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            codeBt.setEnabled(false);
            codeBt.setText("(" + millisUntilFinished / 1000 + ")");
        }

        @Override
        public void onFinish() {
            codeBt.setEnabled(true);
            codeBt.setText("重新获取验证码");
        }
    };

    public static LoginFragment newInstance() {
        return new LoginFragment();
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_login, container, false);
      // ... 省略控件的初始化  
        mPresenter.start();
        return root;
    }

    @Override
    public void showBingPicBg(final String address) {
        try {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Glide.with(getContext()).load(address).into(bgImage);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 显示注册框
     * <p>
     * 显示验证码,显示undo返回键,修改文字
     */
    @Override
    public void showRegister() {}

    @Override
    public void setResult(String result, String msg) {
        if (result.equals("LOGIN_SUCCESS")) {
            Log.d(TAG, "setResult: LOGIN_SUCCESS");
            getActivity().setResult(1);
            getActivity().finish();
        } else {
            Log.d(TAG, "setResult: LOGIN_ERROR");
        }
    }

    /**
     * 显示登录框
     * <p>
     * 隐藏验证码、隐藏undo返回、显示登录按钮
     */
    @Override
    public void showLogin() {}

    @Override
    public void cancelTimer() {
        timer.cancel();
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                codeBt.setEnabled(true);
                codeBt.setText("获取验证码");
            }
        });
    }

    @Override
    public void setPresenter(LoginContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public void showToast(final String message) {}

    @Override
    public void onClick(View v) {
        String account = "";
        String password = "";
        String code = "";
        switch (v.getId()) {
            case R.id.bt_login_login:
                account = accountEt.getText().toString();
                password = passwordEt.getText().toString();
               //... 省略输入验证      
                mPresenter.login(account, password, ACCOUNT_TYPE);
                break;
            case R.id.bt_login_register:
                       //... 省略输入验证        
                        mPresenter.register(user, code, ACCOUNT_TYPE);
                }
                break;
            case R.id.bt_login_undo:
                showLogin();
                break;
            case R.id.bt_login_code:
                //发送短信验证码
                account = accountEt.getText().toString();
                    //验证码倒计时开始
                    timer.start();
                    mPresenter.sendMessage(account, ACCOUNT_TYPE);
                break;
        }
    }
}

LoginPresenter

LoginPresenter要继承LoginContract中的Presenter接口,实现接口中的功能,并在构造函数中创建一个View对象(LoginContact中的View),完成view与presenter的绑定。

public class LoginPresenter implements LoginContract.Presenter {
    public static final String TAG = LoginPresenter.class.getSimpleName();

    private final LoginContract.View mView;

    private UserRepository mUserRepository;

    public LoginPresenter(LoginContract.View view) {
        mUserRepository = UserRepository.getInstance();
        mView = view;
        mView.setPresenter(this);
    }

    @Override
    public void login(final String account, String password, String accountType) {
        LiveService liveService = LiveService.Factory.create();
        Disposable subscribe = liveService.login(account, MD5Util.encrypt(password), accountType)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<Result<String>>() {
                    @Override
                    public void accept(Result<String> stringResult) throws Exception {
                        if (stringResult.getState().equals("200")) {   //登录成功
                            mView.setResult("LOGIN_SUCCESS", stringResult.getMsg());
                            SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MyApplication.getMyApplicationContext()).edit();
                            editor.putString(Constant.SP_ACCOUNT, account);
                            editor.putBoolean(Constant.IS_LOGIN, true);
                            editor.commit();
                        } else if (stringResult.getState().equals("100")) {
                            SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MyApplication.getMyApplicationContext()).edit();
                            editor.putBoolean(Constant.IS_LOGIN, false);
                            editor.commit();
                            mView.setResult("LOGIN_ERROR", stringResult.getMsg());
                            mView.showToast(stringResult.getMsg());
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        mView.showToast("网络异常,请稍后重试");
                    }
                });
    }

    @Override
    public void register(final User user, String code, String accountType) {
        user.setPassword(MD5Util.encrypt(user.getPassword()));
        LiveService liveService = LiveService.Factory.create();
        liveService.register(user.getAccount(), user.getPassword(), user.getName(), code, accountType)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<Result<String>>() {
                    @Override
                    public void accept(Result<String> stringResult) throws Exception {
                        if (stringResult == null) {
                            mView.showToast("网络异常,请稍后重试");
                            return;
                        }
                        String state = stringResult.getState();
                        String msg = stringResult.getMsg();
                        if (state.equals("200")) {
                            //注册成功,将该用户保存到数据库中,并将phone保存到sp中,跳转到登录界面
                            //将sp中的phone加载到登录界面中的phone中
                            mUserRepository.insertUser(user);
                            SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MyApplication.getMyApplicationContext()).edit();
                            editor.putString(Constant.SP_ACCOUNT, user.getAccount());
                            editor.commit();
                            //子线程中能显示吗?
                            mView.showToast(msg);
                            mView.showLogin();
                        } else if (state.equals("100")) {
                            //注册失败,提醒用户注册失败原因
                            mView.showToast(msg);
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.d(TAG, "accept: " + throwable.getMessage());
                        mView.showToast("网络异常,请稍后重试");
                    }
                });
    }

    @Override
    public void sendMessage(String account, String accountType) {
        if (TextUtils.isEmpty(account)) {
            mView.showToast("手机号不能为空");
            return;
        }
        LiveService liveService = LiveService.Factory.create();
        liveService.sendMessage(account, accountType)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Result<String>>() {
                    @Override
                    public void accept(Result<String> stringResult) throws Exception {
                        if (stringResult.getState().equals("200")) {
                            //获取验证码成功
                            mView.showToast("验证码已发送到您的手机");
                        } else {
                            mView.showToast(stringResult.getMsg());
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.d(TAG, "accept: " + throwable.getMessage());
                        mView.showToast("网络错误,稍后重试");
                        mView.cancelTimer();
                    }
                });
    }

    @Override
    public void start() {
        loadBingPic();
    }

    /**
     * 加载每日一图。
     * <p>
     * 获取新的每日一图,成功,则加载到ImageView中,作为loginActivity的背景
     * 获取不成功,则加载原有的每日一图。
     */
    private void loadBingPic() {
        BingPicService bingPicService = BingPicService.Factory.create();
        bingPicService.getBingPic()
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<BingResult>() {
                    @Override
                    public void accept(BingResult bingResult) throws Exception {
                        if (bingResult == null) { //获取不到新的必应每日一图,则用旧图取代
                            showOldBingPicBg();
                            return;
                        }
                        String urlTemp = bingResult.getImages().get(0).getUrl();
                        if (TextUtils.isEmpty(urlTemp)) {//获取不到新的必应每日一图,则用旧图取代
                            showOldBingPicBg();
                            return;
                        } else {
                            String newBingPicURL = "http://s.cn.bing.net" + urlTemp;
                            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getMyApplicationContext());
                            String oldBingPicURL = preferences.getString("BING_PIC", null);
                            if (oldBingPicURL == null || !oldBingPicURL.equals(newBingPicURL)) { //地址不相等
                                SharedPreferences.Editor editor = preferences.edit();
                                editor.putString("BING_PIC", newBingPicURL);
                                editor.commit();
                            }
                            mView.showBingPicBg(newBingPicURL);
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.d(TAG, "onFailure: ");
                        showOldBingPicBg();
                    }
                });
    }
}

在上面的代码中,可以看到login方法中调用了LoginService.Fractory.create()方法创建了一个LiveService,而这个LiveService又是什么呢?这个LiveService就是前面所提到的Retrofit的网络调用接口。调用LiveService的login方法,发起登录的网络请求,同时通过链式调用将网络请求在子线程中进行,请求完成后将登录结果放置到SharePreference中,并调用View中对应的方法,进行页面的控制。

LiveService

Retrofit网络请求的实现,就是通过定义一个接口来完成。这个接口中有不同注解标注的方法,定义了Http请求中的GET、POST、PUT、DELETE等请求方式。而在这个接口中,最为重要的创建Retrofit对象实例,通过在接口中创建工厂类的方式,来创建Retrofit。

public interface LiveService {

    /**
     * 用户登录 - 表单
     *
     * @param account  用户账号:手机/Email
     * @param password 密码
     * @return 返回登录情况
     */
    @FormUrlEncoded
    @POST("login")
    Flowable<Result<String>> login(@Field("account") String account,
                                   @Field("password") String password,
                                   @Field("type") String accountType);

    /**
     * 注册 - 表单
     *
     * @param account  用户账号:手机/Email
     * @param password 密码
     * @param name     用户名称(默认与手机号相同)
     * @param code     验证码
     * @return 返回注册情况
     */
    @FormUrlEncoded
    @POST("register")
    Flowable<Result<String>> register(@Field("account") String account,
                                      @Field("password") String password,
                                      @Field("name") String name,
                                      @Field("code") String code,
                                      @Field("type") String accountType);

    /**
     * 注销
     *
     * @return 返回注销情况
     */
    @GET("logout")
    Flowable<Result<String>> logout();

    /**
     * 发送验证码 - 表单
     *
     * @param account 用户账号:手机/Email
     * @return 返回发送验证码情况
     */
    @FormUrlEncoded
    @POST("message")
    Flowable<Result<String>> sendMessage(@Field("account") String account,
                                         @Field("type") String accountType);

    /**
     * 获取最新版本信息
     *
     * @return
     */
    @GET("version")
    Flowable<Result<String>> getVersion();

    /**
     * 下载差分包
     * 使用streaming方式,retrofit不会一次性将ResponseBody读取如内存,否则用以造成OOM
     *
     * @param url 下载的差分包的地址
     * @return 返回值使用ResponseBody之后会对ResponseBody进行读取
     */
    @GET
    @Streaming
    Flowable<ResponseBody> downloadApk(@Url String url);

    class Factory {
        static boolean isDebug = false;
        static OkHttpClient client = new OkHttpClient
                .Builder()
                .connectTimeout(2, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .addInterceptor(new AddCookiesInterceptor()) //这部分
                .addInterceptor(new ReceivedCookiesInterceptor()) //这部分
                .build();


        public static LiveService create() {
            String url = isDebug ? "本地地址" : "服务器地址";
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(url)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
            return retrofit.create(LiveService.class);
        }
    }
}

编写了LiveService后,Presenter中就完成了登录中数据的本地和网络请求。

MVP模式中的M、V、P都已经实现了,那么问题又来了,如何将Presenter和View相互匹配呢?上述的过程只是将V、P实现了,那他们之间的桥梁要怎么搭建呢?答案就是通过Activity来实现。

LoginActivity

在LoginActivity中创建Fragment和Presenter,然后在Fragment中绑定Presenter,这样就将V、P之间的桥梁搭建了起来。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= 21) { //android 5.0以上系统,状态栏也显示图片
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }
        setContentView(R.layout.activity_login);

        LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (loginFragment == null) {
            loginFragment = LoginFragment.newInstance();
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), loginFragment, R.id.contentFrame);
        }

        mPresenter = new LoginPresenter(loginFragment);
    }

总结

MVP架构下,有许多种变换形式,比如使用原始的数据库操作、使用Litepal数据库、接口回调的方式实现异步请求、Arouter实现页面跳转等,可以根据自身的需求来选择架构的实现形式。

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

推荐阅读更多精彩内容