MVP+Okhttp3+RxJava 简单实现登录操作

介绍

以登录为案列,MVP框架,okhttp网络请求实现的登陆操作
1. 用户输入用户名;
2. 点击登录;
3. 弹出progressbar,告知用户目前正在验证用户名密码;
4. 执行网络请求;
5. 隐藏progressbar;网络验证完成;
6. 根据服务器返回结果执行跳转主界面操作或者显示错误信息;

注意:可能有小伙伴觉得这样写,多了类,多写了代码;
    但增强了代码可读性,可理解性,手动敲一敲便可理解MVP这样设计用意;

效果图

登录界面设计

在这里插入图片描述

知识点

MVP+RxJava+Okhttp
1. MVP 开发框架;
2. RxJava 线程控制;
3. Okhttp 网络请求;
4. Gson 解析json字符串;
5. 安卓基本控件:
            ConstraintLayout
            Textview
            EditText
            Button
            ProgressBar

代码实现

新建项目
这个没什么说的了,new project。
引入三方库
在Module的 build.gradle中加入下方代码;
然后右上角会提示让你同步的 “sync now”,点一下同步完成。
    // 用于网络请求
    implementation("com.squareup.okhttp3:okhttp:3.12.0")
    // 用于切换线程,RxJava RxAndroid 都需要引入
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.4'
    //解析json字符串用
    implementation 'com.google.code.gson:gson:2.8.5'
搭建MVP框架
1. 新建基本的BaseView、BasePresenter
2. 新建包login:所有登录相关的界面、网络操作、数据保存的类都放这里面;
3. 新建view与presenter的连接接口类:ILoginContract;
4. 新建LoginActivity与LoginFragment,activity是fragment的容器,放一个FrameLayout就行;
5. 新建LoginFresenter,用于执行登录相关操作;
6. 新建LoginRepository,用于登录的网络请求;

以下是关键类的代码:
public interface BaseView<T> {
    void setPresenter(T presenter);// 设置 presenter
}
public interface BasePresenter {
    void subscribe();// 订阅 用于绑定view
    void unsubscribe();// 解除与view的绑定
}
/**
LoginFragment 与 LoginPresenter 之间的联系纽带
*/
public interface ILoginContract {

    interface View extends BaseView<Presenter> {
        void showProgressBar(Boolean isShow);//是否显示 progressbar
        void showMessage(String message);// 显示一些展示消息
        void goToMainActivity(User user);// 跳转到主界面,展示用户信息
    }
    interface Presenter extends BasePresenter {
        void doLogin(String name,String pwd);// 执行登录操作
    }
}
/**
用于 LoginFragment 的容器,并将view与presenter 关联起来
*/
public class LoginActivity extends AppCompatActivity  {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        LoginFragment loginFragment = LoginFragment.getInstance();
        getSupportFragmentManager().beginTransaction().add(R.id.fl_container,loginFragment,LoginFragment.class.toString()).commit();
        // 将 presenter 与 view 关联起来
        new LoginPresenter(loginFragment);
    }
}


/**
对应的xml布局 只提供一个 FrameLayout 当容器用,放fragment
*/
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".login.LoginActivity">
<FrameLayout
    android:id="@+id/fl_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
MVP中的 V -> LoginFragment
实现了ILoginContract 中的View接口类
重写view,界面相关的方法,例如控件的显示与隐藏,不关心网络操作是如何实现
public class LoginFragment extends Fragment implements ILoginContract.View {

    private ILoginContract.Presenter presenter;
    private View view;
    private ProgressBar pb;
    private EditText etName;
    private EditText etPwd;
    private Button btnLogin;

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

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_login, container, false);
        initView();
        presenter.subscribe();
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 登录按钮的点击事件
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //执行登录操作
                presenter.doLogin(etName.getText().toString(),etPwd.getText().toString());
            }
        });
    }

    private void initView() {
        pb = view.findViewById(R.id.pb_login);
        etName = view.findViewById(R.id.et_name);
        etPwd = view.findViewById(R.id.et_pwd);
        btnLogin = view.findViewById(R.id.btn_login);
    }

    @Override
    public void showProgressBar(Boolean isShow) {
        if(isShow){
            // 显示 转圈圈
            pb.setVisibility(View.VISIBLE);
        }else {
            // 隐藏
            pb.setVisibility(View.GONE);

        }
    }

    @Override
    public void showMessage(String message) {
        Toast.makeText(getContext(),message,Toast.LENGTH_SHORT).show();
    }

    @Override
    public void goToMainActivity(User user) {
        Intent intent = new Intent(getContext(), MainActivity.class);
        intent.putExtra("user",user);
        getContext().startActivity(intent);
    }

    @Override
    public void setPresenter(ILoginContract.Presenter presenter) {
        if(presenter!=null) this.presenter = presenter;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        presenter.unsubscribe();//解除订阅,
    }
}

 登录界面LoginFragment的xml
使用约束性布局,控制控制之间的相对位置;
根据百分比预留边界空白部分。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity">
<!--留出整个屏幕上部分13%-->
    <android.support.constraint.Guideline
        app:layout_constraintGuide_percent="0.13"
        android:orientation="horizontal"
        android:id="@+id/gl_top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <!--留出整个屏幕左边10%-->
    <android.support.constraint.Guideline
        app:layout_constraintGuide_percent="0.1"
        android:orientation="vertical"
        android:id="@+id/gl_left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!--留出整个屏幕由部分10%-->
    <android.support.constraint.Guideline
        app:layout_constraintGuide_percent="0.9"
        android:orientation="vertical"
        android:id="@+id/gl_right"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:textSize="18sp"
        android:text="用户名"
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        app:layout_constraintLeft_toRightOf="@+id/gl_left"
        app:layout_constraintTop_toBottomOf="@+id/gl_top"
        />
    <TextView
        android:textSize="18sp"
        android:text="密 码"
        android:id="@+id/tv_pwd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/gl_left"
        app:layout_constraintTop_toBottomOf="@+id/tv_name"
        app:layout_constraintRight_toRightOf="@+id/tv_name"
        android:layout_marginTop="10dp"
        />
    
    <EditText
        android:id="@+id/et_name"
        android:hint="请输入用户名"
        android:layout_width="0dp"
        android:layout_height="wrap_content" 
        android:layout_marginLeft="4dp"
        app:layout_constraintLeft_toRightOf="@+id/tv_name"
        app:layout_constraintTop_toTopOf="@+id/tv_name"
        app:layout_constraintBottom_toBottomOf="@+id/tv_name"
        app:layout_constraintRight_toRightOf="@+id/gl_right"
        />
    <EditText
        android:id="@+id/et_pwd"
        android:hint="请输入密码"
        android:inputType="textPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        app:layout_constraintLeft_toLeftOf="@+id/et_name"
        app:layout_constraintTop_toTopOf="@+id/tv_pwd"
        app:layout_constraintBottom_toBottomOf="@+id/tv_pwd"
        app:layout_constraintRight_toRightOf="@+id/gl_right"
        />
    <Button
        android:id="@+id/btn_login"
        android:text="登录"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        app:layout_constraintTop_toBottomOf="@+id/et_pwd"
        android:layout_marginTop="20dp"
        app:layout_constraintLeft_toLeftOf="@id/gl_left"
        app:layout_constraintRight_toRightOf="@id/gl_right"
        />
            <!--用于展示目前正在执行网络请求操作-->
    <ProgressBar
        android:visibility="gone"
        android:id="@+id/pb_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        />
</android.support.constraint.ConstraintLayout>
MVP中的 P->LoginPresenter
ILoginContract 中的Presenter接口类
重写doLogin方法,不关心界面view是如何展示,
不关心网络操作具体实现细节,
只负责数据解析与界面显示的调用;
public class LoginPresenter implements ILoginContract.Presenter {
    private ILoginContract.View view;
    private CompositeDisposable compositeDisposable;// 用于管理网络请求的线程
    private LoginRepository repository;
    private String url = "http://soyoyo.esy.es/testmvp.php";// 测试登录用的url
    private Gson gson;

    public LoginPresenter(ILoginContract.View view) {
        this.view = view;
        this.view.setPresenter(this);
    }

    @Override
    public void doLogin(String name,String pwd) {
        //显示 progressbar
        view.showProgressBar(true);
        Disposable disposable = repository.getUserInfo(url, name, pwd)
                .subscribe(next -> {
                    view.showProgressBar(false);
                    if (next.contains("错误")) {
                        // 登录失败
                        view.showMessage(next);
                    } else {
                        //登录成功
                        view.showMessage("登录成功!");
                        // 解析用户信息
                        User user = gson.fromJson(next, User.class);
                        view.goToMainActivity(user);

                    }
                }, error -> {
                    error.printStackTrace();
                    view.showMessage("登录失败");
                    view.showProgressBar(false);

                });
        compositeDisposable.add(disposable);
    }

    @Override
    public void subscribe() {
        // 初始化变量
        compositeDisposable = new CompositeDisposable();
        repository = new LoginRepository();
        gson = new Gson();
    }

    @Override
    public void unsubscribe() {
        repository = null;//手动置空
        if(compositeDisposable!=null){
            // 关闭所有网络请求,避免内存泄漏
            compositeDisposable.clear();
        }
    }
}
MVP中的 M->LoginRepository
负责网络操作,数据操作相关等
public class LoginRepository {

    private OkHttpClient okHttpClient;


    public LoginRepository(){
        if(okHttpClient ==null){
            okHttpClient = new OkHttpClient()
                            .newBuilder()
                            .callTimeout(10,TimeUnit.SECONDS)// 设置连接超时时间
                            .build();
        }
    }

    public  Observable<String> getUserInfo(String url,String name,String pwd){
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                // 表单方式提交参数
                RequestBody requestBody = new FormBody.Builder()
                        .add("username",name)// username 与 服务器对应,服务器也是通过这个 拿到 name的值
                        .add("userpassword",pwd)
                        .build();
                Request request = new Request.Builder()
                        .url(url)
                        .post(requestBody)
                        .build();
                // 开始请求服务器
                okHttpClient.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        e.printStackTrace();
                        //请求失败
                        emitter.onError(e);
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        //请求成功
                        emitter.onNext(response.body().string());
                    }
                });
            }
        })
                .observeOn(AndroidSchedulers.mainThread())// 指定 最后拿到数据操,解析,显示发生在主线程
                .subscribeOn(Schedulers.io());// 指定 网络请求耗时操作发生在子线程
    }

}
从服务器拿到消息的实体类,其中包涵的字段看代码注释。
public class User implements Serializable {
   private String  name;// 姓名
   private String  age;// 年龄
   private String  sex;// 性别
   private String  describe;//描述
   private String  mobile;//手机号

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
MainActivity:当登录成功后,跳转到的目标界面,显示登录成功从服务器拿到的用户信息;
其中字段命名很明显了,就不一一解释。
public class MainActivity extends AppCompatActivity {

    private TextView tv_name;
    private TextView tv_sex;
    private TextView tv_age;
    private TextView tv_mobile;
    private TextView tv_describe;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        User user = (User) getIntent().getSerializableExtra("user");
        if(user!=null){
            tv_name.setText(tv_name.getText().toString()+""+user.getName());
            tv_sex.setText(tv_sex.getText().toString()+""+user.getSex());
            tv_age.setText(tv_age.getText().toString()+""+user.getAge());
            tv_mobile.setText(tv_mobile.getText().toString()+""+user.getMobile());
            tv_describe.setText(tv_name.getText().toString()+"\n"+user.getDescribe());
        }
    }

    private void initView() {
        tv_name = findViewById(R.id.tv_name);
        tv_sex = findViewById(R.id.tv_sex);
        tv_age = findViewById(R.id.tv_age);
        tv_mobile = findViewById(R.id.tv_mobile);
        tv_describe = findViewById(R.id.tv_describe);
    }
}
最后别忘了在AndroidManifests.xml中添加网络请求的权限
<uses-permission android:name="android.permission.INTERNET"/>

服务器登录文件的编写

简单的用php写的 用户名,密码验证文件
url:http://soyoyo.esy.es/testmvp.php
请求方式:post
用户名:wuming
密码:123
返回信息:
    {
    "name":"无名",
 	"age":"18",
	"sex":"男",
    "describe":"这个人很懒,没有写自我介绍。但是留了一句话:我还会再回来的!",
	"mobile":"13874389438"
     }
<?php
    
    $name = $_POST['username'];
	$pwd = $_POST['userpassword'];
	if($name!="wuming"){
	    die ("用户名错误!");
	}
	
	if($pwd !="123"){
	    die ("密码错误!");
	}

	$info = array('name'=>'无名',
	                'age'=>'18',
	                'sex'=>'男',
	                'describe'=>'这个人很懒,没有写自我介绍。但是留了一句话:我还会再回来的!',
	                'mobile'=>'13874389438');
	echo json_encode($info);
    
?>

总结

熟悉MVP架构
Rxjava的皮毛运用,还有很多的方法,妙用等着去开发实践:map,flitMap,interval等

github代码机票--------->

猜你喜欢

转载自blog.csdn.net/qwe1314225/article/details/85223307