2018年Google的IO大会提出来了Jetpack,上面的一些技术点都是这个里面的,正好公司新开了一个项目,所以抱着尝尝鲜的态度来感受一下(内容有点多,基本上都是代码)。
这里就不多讲这些技术的优点,Google和百度上面一堆的介绍。
这个demo的主要功能就是登陆并且将从服务器获取的数据存到本地数据库然后读取出来(还是很简单的)。后台服务器是用的我之前写过的一个SpringBoot小项目,感兴趣的地址
这里网络请求是采用的Retrofit+RxJava2
/**
* api商店
*/
public interface ApiStore {
@POST("user/login")
Flowable<BaseResult<UserEntity>> login(@Body RequestBody body);
}
然后对retrofit和okhttp进行初始化
public class RetrofitClient {
private static final String TAG="client";
private static OkHttpClient okHttpClient;
private static Retrofit retrofit;
public static ApiStore getInstance(){
if (okHttpClient==null){
ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(MyApplication.getContext()));
okHttpClient=new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor(msg-> {
//访问网络请求,和服务端响应请求时。将数据拦截并输出
Log.e(TAG, "log: " + msg);
}).setLevel(HttpLoggingInterceptor.Level.BODY))
.connectTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
.readTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(NetConstant.TIME_OUT, TimeUnit.SECONDS)
.cookieJar(cookieJar)
.build();
} if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(NetConstant.BASE_URL) //BaseUrl
.client(okHttpClient) //请求的网络框架
.addConverterFactory(GsonConverterFactory.create()) //解析数据格式
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 使用RxJava作为回调适配器
.build();
}
return retrofit.create(ApiStore.class);
}
}
然后编写一个API管理类,负责处理这些方法
public class ApiManager {
public static Flowable<BaseResult<UserEntity>> login(LoginParams params){
return RetrofitClient.getInstance().login(NetUtils.ParamsToBody(params));
}
}
那个NetUtils工具类是负责把一些参数转换成我们需要Requestbody 。BaseResult是json数据的外层,这里就不列出来了。
接下来对RxJava进行封装一下吧,让它变得更加好用一点
public class BaseHttpSubscriber<T> implements Subscriber<BaseResult<T>> {
private ApiException apiException;
private MutableLiveData<BaseResult<T>> data;
public BaseHttpSubscriber(){
data= new MutableLiveData();
}
public MutableLiveData<BaseResult<T>> get(){
return data;
}
public void set(BaseResult<T> d){
data.setValue(d);
}
@Override
public void onSubscribe(Subscription s) {
s.request(1);
}
@Override
public void onNext(BaseResult<T> tBaseResult) {
if (tBaseResult.getCode()== NetConstant.SUCCESS_CODE){
onFinish(tBaseResult);
}else {
apiException= ExceptionEngine.handleException(new ServerException(tBaseResult.getCode(),tBaseResult.getMessage()));
getErrorResult(apiException);
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
public void onFinish(BaseResult<T> t){
set(t);
}
private void getErrorResult(ApiException e){
BaseResult baseResult=new BaseResult();
baseResult.setCode(e.getCode());
baseResult.setMessage(e.getMessage());
onFinish(baseResult);
}
}
这里主要是首先对数据进行拦截处理,判断code是否合法,因为我们要通过LiveData来进行数据回调处理,所以这需要把数据包装成LiveData形式通过使用setValue设置LiveData数据观察者那边就会收到对应通知。
我们数据获取都是通过Repository去拿的,所以这里需要封装一下BaseRepository
public class BaseRepository<T> {
private BaseHttpSubscriber<T> baseHttpSubscriber;
private Flowable<BaseResult<T>> flowable;
public BaseHttpSubscriber<T> send(){
flowable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(baseHttpSubscriber);
return baseHttpSubscriber;
}
/**
* 初始化
*/
public BaseRepository() {
baseHttpSubscriber = new BaseHttpSubscriber<>();
}
public BaseRepository<T> request(Flowable<BaseResult<T>> flowable) {
this.flowable = flowable;
return this;
}
}
我们拿到数据后肯定是要存到本地的,这里采用Room,这个也是需要我们进行一下配置的。
Room是可以结合LiveData和RxJava一起使用的,毕竟全家桶,这点还是可以的,相比之下GreenDao还得自己去处理才能使用LiveData。
@Entity
public class UserEntity {
@PrimaryKey(autoGenerate = true)
private int id;
/**
* user_id : 1000
* username : 盖伦
* password : 1314520
* sex : 0
* nickname : 德玛西亚之力
* phone : 15575818133
* token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAwMCwiZXhwIjoxNTU1NTU1NjU1fQ.SpKwXt_9be2UcSP3dXGj5zIBquwIsi5carEHtqAH9QQ
* role_id : 1
* leave : 1
*/
private int user_id;
private String username;
private String password;
private int sex;
private String nickname;
private String phone;
private String token;
private int role_id;
private int leave;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
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;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public int getRole_id() {
return role_id;
}
public void setRole_id(int role_id) {
this.role_id = role_id;
}
public int getLeave() {
return leave;
}
public void setLeave(int leave) {
this.leave = leave;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "UserEntity{" +
"user_id=" + user_id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex=" + sex +
", nickname='" + nickname + '\'' +
", phone='" + phone + '\'' +
", token='" + token + '\'' +
", role_id=" + role_id +
", leave=" + leave +
'}';
}
}
加上@Entity这个注解,它就会对应生成这些字段
@PrimaryKey这个一定是要加的,主键
接下来编写Dao类,里面主要是添加一些我们自己需要的方法,CRUD之类的
@Dao
public interface UserDao {
@Insert
void insert(UserEntity entity);
@Query("SELECT * FROM UserEntity WHERE id=1")
UserEntity getUserOne();
@Update
void update(UserEntity entity);
@Query("SELECT * FROM UserEntity ")
List<UserEntity> getUser();
@Query("SELECT * FROM UserEntity")
Flowable<UserEntity> getUserByRxJava();
@Query("SELECT * FROM UserEntity WHERE id=1")
LiveData<UserEntity> getUserByLiveData();
}
最后两个方法是一个采用rxjava一个是livedata。
然后编写一个UserDataBases,通过这个来初始化数据库。必须要抽象类…
@Database(entities = {UserEntity.class}, version = 1)
public abstract class UserDataBases extends RoomDatabase {
private static volatile UserDataBases dataBases;
private static final String DATA_TABLE_NAME="userdb";
private final MutableLiveData mIsDatabaseCreated = new MutableLiveData<>();
public abstract UserDao userDao();
public static UserDataBases getInstance(){
if (dataBases==null){
synchronized (UserDataBases.class){
if (dataBases==null){
dataBases=buildDatabase(MyApplication.getContext(), MyApplication.getAppExecutors());
dataBases.updateDatabaseCreated(MyApplication.getContext().getApplicationContext());
}
}
}
return dataBases;
}
/**
* Build the database. {@link Builder#build()} only sets up the database configuration and
* creates a new instance of the database.
* The SQLite database is only created when it's accessed for the first time.
*/
private static UserDataBases buildDatabase(final Context appContext,
final AppExecutors executors) {
return Room.databaseBuilder(appContext, UserDataBases.class, DATA_TABLE_NAME)
.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
}
})
.build();
}
/**
* Check whether the database already exists and expose it via {@link #getDatabaseCreated()}
*/
private void updateDatabaseCreated(final Context context) {
if (context.getDatabasePath(DATA_TABLE_NAME).exists()) {
setDatabaseCreated();
}
}
public LiveData getDatabaseCreated() {
return mIsDatabaseCreated;
}
private void setDatabaseCreated(){
mIsDatabaseCreated.postValue(true);
}
}
最后room这快差不多搞定了,接下来编写和我们业务逻辑有关的东西,开始不是编写了一个仓库基类嘛,现在我们需要编写它的实现类。先写个接口,声明一些需要的方法
public interface ILoginRepository {
LiveData<BaseResult<UserEntity>> login(LoginParams params);
}
public class LoginRepository extends BaseRepository<UserEntity> implements ILoginRepository {
private UserDataBases mDatabases;
public LoginRepository() {
mDatabases=UserDataBases.getInstance();
}
@Override
public MutableLiveData<BaseResult<UserEntity>> login(LoginParams params) {
return request(ApiManager.login(params)).send().get();
}
public void insertUser(UserEntity entity){
mDatabases.userDao().insert(entity);
}
public void updateUser(UserEntity entity){
mDatabases.userDao().update(entity);
}
public UserEntity getUserOne(){
return mDatabases.userDao().getUserOne();
}
public LiveData<UserEntity>getUserByLiveData(){
return mDatabases.userDao().getUserByLiveData();
}
}
这里没啥好说的,都是对数据的一些处理,登录获取数据和操作数据库。
我们的一些业务逻辑操作都交给ViewModel来处理,有点类似于MVP中的P哦,
public class UserModel extends ViewModel {
public LoginRepository mLoginRepository;
private MutableLiveData<BaseResult<UserEntity>> mutableLiveData;
private MutableLiveData<UserEntity> userEntityMutableLiveData;
public UserModel(){
mLoginRepository=new LoginRepository();
userEntityMutableLiveData=new MutableLiveData<>();
}
public LiveData<BaseResult<UserEntity>> getUserLiveData(){
return mutableLiveData;
}
public LiveData<UserEntity> getUserEntityData(){
return userEntityMutableLiveData;
}
public void login(String phone, String pwd){
LoginParams params=new LoginParams();
params.setPhone(phone);
params.setPassword(pwd);
this.mutableLiveData=mLoginRepository.login(params);
}
public void insertUser(UserEntity userEntity){
MyApplication.getAppExecutors().diskIO().execute(new Runnable() {
@Override
public void run() {
userEntity.setId(1);
UserEntity userOne = mLoginRepository.getUserOne();
if (userOne==null){
mLoginRepository.insertUser(userEntity);
}else {
mLoginRepository.updateUser(userEntity);
}
}
});
}
}
我们在insert数据的时候是开启了一个线程来进行操作的,当然也可以使用rxjava,先判断数据库中是否有数据,有就更新,没有就插入。
最后就是Activity了,
public class MainActivity extends AppCompatActivity {
@BindView(R.id.login_user_num_et)
AppCompatEditText mPhoneText;
@BindView(R.id.login_pwd_num_et)
AppCompatEditText mPwdText;
private UserModel mModel;
Observer<BaseResult<UserEntity>> observer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mModel=ViewModelProviders.of(this).get(UserModel.class);
observer =new Observer<BaseResult<UserEntity>>() {
@Override
public void onChanged(BaseResult<UserEntity> userEntityBaseResult) {
Log.e("dandy","success ");
mModel.insertUser(userEntityBaseResult.getData());
}
};
mModel.mLoginRepository.getUserByLiveData().observe(this, new Observer<UserEntity>() {
@Override
public void onChanged(UserEntity entity) {
if (entity==null){
Log.e("dandy","数据库里面没有数据 ");
}else {
Log.e("dandy","数据库中读取出来的 "+entity.toString());
}
}
});
}
@OnClick(R.id.login_submit_btn)
public void onLogin(){
mModel.login(mPhoneText.getText().toString(), mPwdText.getText().toString());
mModel.getUserLiveData().observe(this, observer);
}
}
注:观察者只能初始化一次,不能多次初始化,不然会回调多次的。
当我们对数据库进行操作的时候,会自动响应读取数据库,便于我们及时获取最新的数据
最后我们跑一下试试,看看日志,
额,What fuck?好吧,Google为了安全真是煞费苦心,
这是API>=27的时候才会出现的,认定我的后台域名不安全,加点东西就可以了,在res目录下新建xml文件夹,然后新增network_security_config.xml
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
network-security-config>
最后在AndroidManifest中application节点下加入
android:networkSecurityConfig="@xml/network_security_config"就OJBK了。
附上demo地址