一、工程项目
包名解释:
base:基类库。
BaseDto类为服务器返回公共实体;BaseHttpSubscriber类自定义请求服务器被观察者;BaseRepository类请求网络数据基类。
exception:异常类模块。
ApiException类前端自定义Exception;ServerException类服务器返回的Exception;ExceptionEngine类拦截各种异常处理。
https:Retrofit+OkHttp封装网络请求模块。
ApiService接口API的Retrofit注解;RequetRetrofit类的网络请求用到的Retrofit+OkHttp。
interceptor:自定义网络请求拦截器。
model:数据模型层。定义实体类。
repository:数据仓库。包括网络数据获取,sqlite小型数据库,文件File,SharedPreferences数据存储。
view:视图层。主要包含Activity+Fragment实体类。
viewmodel:VM视图模型层。
二、引用类库
implementation 'com.alibaba:fastjson:1.1.70.android' implementation 'com.google.code.gson:gson:2.8.4' implementation 'com.squareup.okhttp3:okhttp:3.11.0' //Rxlifecycle implementation 'com.trello:rxlifecycle:0.3.1' implementation 'com.trello:rxlifecycle-components:0.3.1' implementation 'com.github.bumptech.glide:glide:4.8.0' implementation 'com.squareup.retrofit2:converter-gson:2.+' //必须使用 implementation 'com.lzy.net:okgo:3.0.4' implementation 'com.squareup.okio:okio:1.5.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.+' implementation 'com.squareup.retrofit2:retrofit:2.+' implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' implementation 'com.squareup.retrofit2:converter-gson:2.+' implementation 'com.squareup.retrofit2:converter-scalars:2.0.0' implementation 'com.facebook.stetho:stetho:1.4.2' implementation 'com.facebook.stetho:stetho-okhttp3:1.4.2' // 依赖以下两个库,会自动引用基础库与Android库 implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0' implementation 'com.trello.rxlifecycle2:rxlifecycle-navi:2.1.0' implementation 'com.tbruyelle.rxpermissions:rxpermissions:[email protected]' implementation 'com.jakewharton.rxbinding:rxbinding:1.+' // Lifecycles, LiveData 和 ViewModel implementation "android.arch.lifecycle:extensions:1.1.0" // 对 RxJava 的支持 implementation "android.arch.persistence.room:rxjava2:1.0.0-alpha5"
三、网络请求封装
1、BaseDto类
package com.ylink.frameworkdemo.base; import java.io.Serializable; /** * 服务器返回公共实体 * * @param <T> * @author twilight * @Time 2019-07-21 */ public class BaseDto<T> implements Serializable { private String statusCode; private String statusDesc; private T data; public String getStatusCode() { return statusCode; } public void setStatusCode(String statusCode) { this.statusCode = statusCode; } public String getStatusDesc() { return statusDesc; } public void setStatusDesc(String statusDesc) { this.statusDesc = statusDesc; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
2、BaseHttpSubscriber类
package com.ylink.frameworkdemo.base; import androidx.lifecycle.MutableLiveData; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.exception.ApiException; import com.ylink.frameworkdemo.exception.ExceptionEngine; import com.ylink.frameworkdemo.exception.ServerException; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; /** * 自定义请服务器被观察者 * * @author twilight * @Time 2019-07-21 */ public class BaseHttpSubscriber<T> implements Subscriber<BaseDto<T>> { //异常类 private ApiException ex; public BaseHttpSubscriber() { data = new MutableLiveData(); } private MutableLiveData<BaseDto<T>> data; public MutableLiveData<BaseDto<T>> get() { return data; } public void set(BaseDto<T> t) { this.data.setValue(t); } public void onFinish(BaseDto<T> t) { set(t); } @Override public void onSubscribe(Subscription s) { // 观察者接收事件 = 1个 s.request(1); } @Override public void onNext(BaseDto<T> t) { if (t.getStatusCode().equals(Constant.RespCode.R000)) { onFinish(t); } else{ ex = ExceptionEngine.handleException(new ServerException(t.getStatusCode(), t.getStatusDesc())); getErrorDto(ex); } } @Override public void onError(Throwable t) { ex = ExceptionEngine.handleException(t); getErrorDto(ex); } /** * 初始化错误的dto * * @param ex */ private void getErrorDto(ApiException ex) { BaseDto dto = new BaseDto(); dto.setStatusCode(ex.getStatusCode()); dto.setStatusDesc(ex.getStatusDesc()); onFinish((BaseDto<T>) dto); } @Override public void onComplete() { } }
3、BaseRepository类
package com.ylink.frameworkdemo.base; import io.reactivex.Flowable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; /** * Repository基类 * * @author twilight * @Time 2019-07-21 */ public class BaseRepository { /** * 请求网络 * @param flowable * @param <T> * @return */ public <T> BaseHttpSubscriber<T> request(Flowable<BaseDto<T>> flowable){ BaseHttpSubscriber<T> baseHttpSubscriber = new BaseHttpSubscriber<>(); //RxJava Subscriber回调 flowable.subscribeOn(Schedulers.io()) //解决背压 .observeOn(AndroidSchedulers.mainThread()) .subscribe(baseHttpSubscriber); return baseHttpSubscriber; } }
4、ApiException类
package com.ylink.frameworkdemo.exception; /** * 前端自定义Exception */ public class ApiException extends Exception { private String statusCode;//错误码 private String statusDesc;//错误信息 public ApiException(Throwable throwable, String statusCode) { super(throwable); this.statusCode = statusCode; } public ApiException(String statusCode, String statusDesc) { this.statusCode = statusCode; this.statusDesc = statusDesc; } public String getStatusCode() { return statusCode; } public void setStatusCode(String statusCode) { this.statusCode = statusCode; } public String getStatusDesc() { return statusDesc; } public void setStatusDesc(String statusDesc) { this.statusDesc = statusDesc; } }
5、ServerException类
package com.ylink.frameworkdemo.exception; /** * 服务器返回的Exception */ public class ServerException extends RuntimeException { private String statusCode;//错误码 private String statusDesc;//错误信息 public ServerException(String statusCode, String statusDesc) { this.statusCode = statusCode; this.statusDesc = statusDesc; } public String getStatusCode() { return statusCode; } public void setStatusCode(String statusCode) { this.statusCode = statusCode; } public String getStatusDesc() { return statusDesc; } public void setStatusDesc(String statusDesc) { this.statusDesc = statusDesc; } }
6、ExceptionEngine类
package com.ylink.frameworkdemo.exception; import android.net.ParseException; import android.util.MalformedJsonException; import com.google.gson.JsonParseException; import org.json.JSONException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import retrofit2.HttpException; public class ExceptionEngine { //客户端报错 public static final int UN_KNOWN_ERROR = 9000;//未知错误 public static final int ANALYTIC_SERVER_DATA_ERROR = 9001;//解析(服务器)数据错误 public static final int ANALYTIC_CLIENT_DATA_ERROR = 9002;//解析(客户端)数据错误 public static final int CONNECT_ERROR = 9003;//网络连接错误 public static final int TIME_OUT_ERROR = 9004;//网络连接超时 public static final int UNKNOWNHOSTEXCEPTION = 9005;//网络连接超时 public static ApiException handleException(Throwable e) { ApiException ex; if (e instanceof HttpException) { //HTTP错误 HttpException httpExc = (HttpException) e; ex = new ApiException(e, String.valueOf(httpExc.code())); ex.setStatusDesc("网络错误,请稍后再试"); //均视为网络错误 return ex; } else if (e instanceof ServerException) { //服务器返回的错误 ServerException serverExc = (ServerException) e; ex = new ApiException(serverExc, serverExc.getStatusCode()); ex.setStatusDesc(serverExc.getStatusDesc()); return ex; } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException || e instanceof MalformedJsonException) { //解析数据错误 ex = new ApiException(e, String.valueOf(ANALYTIC_SERVER_DATA_ERROR)); ex.setStatusDesc("客户端异常,请稍后再试"); return ex; } else if (e instanceof ConnectException) {//连接网络错误 ex = new ApiException(e, String.valueOf(CONNECT_ERROR)); ex.setStatusDesc("网络连接错误,请稍后再试"); return ex; } else if (e instanceof SocketTimeoutException) {//网络超时 ex = new ApiException(e, String.valueOf(TIME_OUT_ERROR)); ex.setStatusDesc("网络连接超时,请稍后再试"); return ex; } else if (e instanceof UnknownHostException) {//网络异常 ex = new ApiException(e, String.valueOf(UNKNOWNHOSTEXCEPTION)); ex.setStatusDesc("网络异常,请检查您的网络连接"); return ex; } else { //未知错误 ex = new ApiException(e, String.valueOf(UN_KNOWN_ERROR)); ex.setStatusDesc("系统异常,请稍后再试"); return ex; } } }
7、ApiService接口
package com.ylink.frameworkdemo.https; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.base.BaseDto; import com.ylink.frameworkdemo.model.dto.LoginDto; import com.ylink.frameworkdemo.model.vo.LoginVo; import io.reactivex.Flowable; import retrofit2.http.Body; import retrofit2.http.POST; /** * api接口 * * @author twilight * @Time 2019-07-21 * * retrofit的注解学习https://blog.****.net/qiang_xi/article/details/53959437 */ public interface ApiService { /** * 登录 * @param loginVo * @return */ @POST(Constant.Server.LOGIN) Flowable<BaseDto<LoginDto>> login(@Body LoginVo loginVo); }
8、RequestRetrofit类
package com.ylink.frameworkdemo.https; import android.util.Log; import com.facebook.stetho.okhttp3.StethoInterceptor; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.interceptor.AddCookiesInterceptor; import com.ylink.frameworkdemo.interceptor.ReceivedCookiesInterceptor; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; /** * 普通的网络请求用到的Retrofit */ public class RequetRetrofit { private static final String TAG = "RequetRetrofit"; /** * 创建okhttp相关对象 */ private static OkHttpClient okHttpClient; /** * 创建Retrofit相关对象 */ private static Retrofit retrofit; public static <T> T getInstance(final Class<T> service) { if (okHttpClient == null) { synchronized (RequetRetrofit.class) { if(okHttpClient == null) { /** * 创建okhttp相关对象 */ okHttpClient = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { //访问网络请求,和服务端响应请求时。将数据拦截并输出 Log.d(TAG, "log: " + message); } }).setLevel(HttpLoggingInterceptor.Level.BODY)) //Log等级 .connectTimeout(Constant.Server.TIME_OUT, TimeUnit.SECONDS) //超时时间 .readTimeout(Constant.Server.TIME_OUT, TimeUnit.SECONDS) .writeTimeout(Constant.Server.TIME_OUT, TimeUnit.SECONDS) .addNetworkInterceptor(new StethoInterceptor()) .addInterceptor(new AddCookiesInterceptor()) // .addInterceptor(new ReceivedCookiesInterceptor()) .build(); } } } if (retrofit == null) { synchronized (RequetRetrofit.class) { if(retrofit == null) { retrofit = new Retrofit.Builder() .baseUrl(Constant.Server.ROOT_URL) //BaseUrl .client(okHttpClient) //请求的网络框架 .addConverterFactory(GsonConverterFactory.create()) //解析数据格式 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 使用RxJava作为回调适配器 .build(); } } } return retrofit.create(service); } }
9、AddCookiesInterceptor类
package com.ylink.frameworkdemo.interceptor; import com.alibaba.fastjson.JSONObject; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.utils.SPUtil; import java.io.IOException; import java.util.List; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; /** * 自定义拦截器刷新sessionId 非首次请求的处理 * @author twilight * @Time 2019-07-21 */ public class AddCookiesInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); String cookieStr = SPUtil.getData(Constant.SP.SP, Constant.SP.SESSION_ID, String.class, null); List<String> cookies = JSONObject.parseArray(cookieStr, String.class); if (cookies != null) { for (String cookie : cookies) { builder.addHeader("Cookie", cookie); } } return chain.proceed(builder.build()); } }
10、ReceivedCookiesInterceptor类
package com.ylink.frameworkdemo.interceptor; import com.alibaba.fastjson.JSONObject; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.utils.SPUtil; import java.io.IOException; import java.util.ArrayList; import java.util.List; import okhttp3.Interceptor; import okhttp3.Response; /** * 自定义拦截器刷新sessionId 首次请求的处理 * @author twilight * @Time 2019-07-21 */ public class ReceivedCookiesInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); if (!originalResponse.headers("Set-Cookie").isEmpty()) { List<String> cookies = new ArrayList<>(); for (String header : originalResponse.headers("Set-Cookie")) { cookies.add(header); } String cookieStr = JSONObject.toJSONString(cookies); SPUtil.putData(Constant.SP.SP, Constant.SP.SESSION_ID, cookieStr); } return originalResponse; } }
四、创建model层
1、登录Vo
package com.ylink.frameworkdemo.model.vo; import java.io.Serializable; /** * 登陆vo * * @author twilight * @Time 2020-01-14 */ public class LoginVo implements Serializable { private String password; private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
2、登录Dto
package com.ylink.frameworkdemo.model.dto; import java.io.Serializable; /** * 登陆返回实体 * * @author twilight * @Time 2020-01-14 */ public class LoginDto implements Serializable { private String id; private String userName; private String userAccount; private String sex; private String age; private String telephone; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserAccount() { return userAccount; } public void setUserAccount(String userAccount) { this.userAccount = userAccount; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } }
五、创建Repository
1、ILoginRepository接口类
package com.ylink.frameworkdemo.repository.network.impl; import androidx.lifecycle.LiveData; import com.ylink.frameworkdemo.base.BaseDto; import com.ylink.frameworkdemo.model.dto.LoginDto; import com.ylink.frameworkdemo.model.vo.LoginVo; /** * 登录 */ public interface ILoginRepository { /** * 登录 * @param loginVo * @return */ LiveData<BaseDto<LoginDto>> login(LoginVo loginVo); }
2、LoginRepository接口实现类
package com.ylink.frameworkdemo.repository.network; import androidx.lifecycle.LiveData; import com.ylink.frameworkdemo.base.BaseDto; import com.ylink.frameworkdemo.base.BaseRepository; import com.ylink.frameworkdemo.https.ApiService; import com.ylink.frameworkdemo.https.RequetRetrofit; import com.ylink.frameworkdemo.model.dto.LoginDto; import com.ylink.frameworkdemo.model.vo.LoginVo; import com.ylink.frameworkdemo.repository.network.impl.ILoginRepository; /** * 登录 */ public class LoginRepository extends BaseRepository implements ILoginRepository { /** * 登录 * @param loginVo * @return */ @Override public LiveData<BaseDto<LoginDto>> login(LoginVo loginVo) { return request(RequetRetrofit.getInstance(ApiService.class).login(loginVo)).get(); } }
六、创建View层
1、创建activity_login.xml文件
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".view.login.LoginActivity"> <EditText android:id="@+id/username" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="96dp" android:layout_marginEnd="24dp" android:hint="用户名" android:inputType="textEmailAddress" android:selectAllOnFocus="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/password" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:hint="密码" android:imeOptions="actionDone" android:inputType="textPassword" android:selectAllOnFocus="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/username" /> <Button android:id="@+id/login" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start" android:layout_marginStart="48dp" android:layout_marginTop="16dp" android:layout_marginEnd="48dp" android:layout_marginBottom="64dp" android:enabled="true" android:text="登录" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/password" app:layout_constraintVertical_bias="0.2" /> </androidx.constraintlayout.widget.ConstraintLayout>
2、创建LoginActivity类
package com.ylink.frameworkdemo.view.login; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.ylink.frameworkdemo.Constant; import com.ylink.frameworkdemo.R; import com.ylink.frameworkdemo.base.BaseDto; import com.ylink.frameworkdemo.model.dto.LoginDto; import com.ylink.frameworkdemo.model.vo.LoginVo; import com.ylink.frameworkdemo.viewmodel.login.LoginViewmodel; public class LoginActivity extends AppCompatActivity { EditText usernameEditText; EditText passwordEditText; Button loginButton; private LoginViewmodel viewmodel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); viewmodel = ViewModelProviders.of(this).get(LoginViewmodel.class); usernameEditText = findViewById(R.id.username); passwordEditText = findViewById(R.id.password); loginButton = findViewById(R.id.login); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { login(); } }); } /** * 登录 */ private void login(){ String username = usernameEditText.getText().toString().trim(); String password = passwordEditText.getText().toString().trim(); LoginVo loginVo = new LoginVo(); loginVo.setUsername(username); loginVo.setPassword(password); viewmodel.login(loginVo).observe(this, new Observer<BaseDto<LoginDto>>() { @Override public void onChanged(@Nullable BaseDto<LoginDto> loginDtoBaseDto) { if(loginDtoBaseDto.getStatusCode().equals(Constant.RespCode.R000)){ Toast.makeText(LoginActivity.this,"登录成功",Toast.LENGTH_LONG).show(); }else{ Toast.makeText(LoginActivity.this,loginDtoBaseDto.getStatusDesc(),Toast.LENGTH_LONG).show(); } } }); } }
七、创建ViewModel层
package com.ylink.frameworkdemo.viewmodel.login; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; import com.ylink.frameworkdemo.base.BaseDto; import com.ylink.frameworkdemo.model.dto.LoginDto; import com.ylink.frameworkdemo.model.vo.LoginVo; import com.ylink.frameworkdemo.repository.network.LoginRepository; import com.ylink.frameworkdemo.repository.network.impl.ILoginRepository; /** * 登录页面viewmodel */ public class LoginViewmodel extends ViewModel { public LiveData<BaseDto<LoginDto>> login(LoginVo loginVo){ ILoginRepository loginRepository = new LoginRepository(); return loginRepository.login(loginVo); } }