介绍
以登录为案列,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代码机票--------->