介绍
以登录为案列,MVP框架,okhttp网络请求实现的登陆操作
1. 用户输入用户名;
2. 点击登录;
3. 弹出progressbar,告知用户目前正在验证用户名密码;
4. 执行网络请求;
5. 隐藏progressbar;网络验证完成;
6. 根据服务器返回结果执行跳转主界面操作或者显示错误信息;注意:可能有小伙伴觉得这样写,多了类,多写了代码;但增强了代码可读性,可理解性,手动敲一敲便可理解MVP这样设计用意;
效果图
知识点
MVP+RxJava+Okhttp
1. MVP 开发框架;
2. RxJava 线程控制;
3. Okhttp 网络请求;
4. Gson 解析json字符串;
5. 安卓基本控件:ConstraintLayoutTextviewEditTextButtonProgressBar
代码实现
新建项目
这个没什么说的了,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();// 订阅 用于绑定viewvoid unsubscribe();// 解除与view的绑定
}
/**
LoginFragment 与 LoginPresenter 之间的联系纽带
*/
public interface ILoginContract {interface View extends BaseView<Presenter> {void showProgressBar(Boolean isShow);//是否显示 progressbarvoid 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 {@Overrideprotected 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">
<FrameLayoutandroid: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@Overridepublic 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;}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 登录按钮的点击事件btnLogin.setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}@Overridepublic void showProgressBar(Boolean isShow) {if(isShow){// 显示 转圈圈pb.setVisibility(View.VISIBLE);}else {// 隐藏pb.setVisibility(View.GONE);}}@Overridepublic void showMessage(String message) {Toast.makeText(getContext(),message,Toast.LENGTH_SHORT).show();}@Overridepublic void goToMainActivity(User user) {Intent intent = new Intent(getContext(), MainActivity.class);intent.putExtra("user",user);getContext().startActivity(intent);}@Overridepublic void setPresenter(ILoginContract.Presenter presenter) {if(presenter!=null) this.presenter = presenter;}@Overridepublic 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.Guidelineapp: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.Guidelineapp: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.Guidelineapp:layout_constraintGuide_percent="0.9"android:orientation="vertical"android:id="@+id/gl_right"android:layout_width="match_parent"android:layout_height="wrap_content" /><TextViewandroid: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"/><TextViewandroid: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"/><EditTextandroid: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"/><EditTextandroid: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"/><Buttonandroid: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"/><!--用于展示目前正在执行网络请求操作--><ProgressBarandroid: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";// 测试登录用的urlprivate Gson gson;public LoginPresenter(ILoginContract.View view) {this.view = view;this.view.setPresenter(this);}@Overridepublic void doLogin(String name,String pwd) {//显示 progressbarview.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);}@Overridepublic void subscribe() {// 初始化变量compositeDisposable = new CompositeDisposable();repository = new LoginRepository();gson = new Gson();}@Overridepublic 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>() {@Overridepublic 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() {@Overridepublic void onFailure(Call call, IOException e) {e.printStackTrace();//请求失败emitter.onError(e);}@Overridepublic 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;@Overrideprotected 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代码机票--------->