导依赖
implementation 'com.android.support:cardview-v7:26.1.0' implementation "android.arch.lifecycle:extensions:1.0.0" implementation "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0" implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0' implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.okhttp3:okhttp:3.8.0' implementation 'com.squareup.retrofit2:converter-gson:2.1.0' implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.24' implementation 'com.github.bumptech.glide:glide:3.8.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
1.网络工具类 使用Retrofit进行网络请求
package win.canking.mvvmarch.module_essay.net; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MediatorLiveData; import android.support.annotation.WorkerThread; import java.io.IOException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Call; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import win.canking.mvvmarch.architecture.IRequestApi; import win.canking.mvvmarch.module_essay.db.entity.ZhihuItemEntity; import win.canking.mvvmarch.net.NetConstant; /** * Created by changxing on 2017/12/4. */ public class NetEngine { private Retrofit mRetrofit; private volatile NetEngine mInstance; private static class Holder { static NetEngine netEngine = new NetEngine(); } private NetEngine() { mRetrofit = new Retrofit.Builder() .baseUrl(NetConstant.URL_BASE) .client(getFreeClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); } private OkHttpClient getFreeClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); X509TrustManager[] trustManager = new X509TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } } }; try { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustManager, new SecureRandom()); SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); if (sslSocketFactory != null) builder.sslSocketFactory(sslSocketFactory); builder.hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); builder.addInterceptor(logging); builder.connectTimeout(20, TimeUnit.SECONDS); builder.writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS); builder.retryOnConnectionFailure(true); return builder.build(); } public static NetEngine getInstance() { return Holder.netEngine; } @WorkerThread public <ResultType> LiveData2.MainActivity中逻辑没什么,真正的UI界面时EssayFragment,主要逻辑是:ResultType>> getEssay(@EssayWebService.EssayType String type) throws IOException { EssayWebService api = mRetrofit.create(EssayWebService.class); Call essayCall = api.getZhihuList("latest"); MediatorLiveData ResultType>> result = new MediatorLiveData<>(); final Response response = essayCall.execute(); IRequestApi<ResultType> requestApi = new IRequestApi<ResultType>() { @Override public ResultType getBody() { ZhihuItemEntity entity = response.body(); return (ResultType) entity; } @Override public String getErrorMsg() { return response.message(); } @Override public boolean isSuccessful() { return response.isSuccessful(); } }; result.postValue(requestApi); return result; } }
initView()主要初始化ViewModel,adapter,和各个控件
subscribeUi()方法主要将数据绑定给UI界面,使用viewmodel从数据库中查询获取数据,
private void subscribeUi() { viewModel.getEssayData().observe(this, new ObserverupdateUI()主要将数据传输给Adapter>() { @Override public void onChanged(@Nullable Resource essayDayEntityResource) { if (essayDayEntityResource != null && essayDayEntityResource.data != null) { if (essayDayEntityResource.status == Resource.Status.SUCCEED) { updateUI(essayDayEntityResource.data); Toast.makeText(getActivity(), "succeed", Toast.LENGTH_SHORT).show(); } else if (essayDayEntityResource.status == Resource.Status.LOADING) { Toast.makeText(getActivity(), "DB loaded " + essayDayEntityResource.message, Toast.LENGTH_SHORT).show(); } else if (essayDayEntityResource.status == Resource.Status.ERROR) { Toast.makeText(getActivity(), "error", Toast.LENGTH_SHORT).show(); } } refreshLayout.setRefreshing(false); } }); }
private void updateUI(@NonNull ZhihuItemEntity entity) { Listlist = new ArrayList<>(); for (ZhihuStoriesEntity enti : entity.getStories()) { EssayListAdapter.MultiItem item = new EssayListAdapter.MultiItem(enti, TYPE_BASE); list.add(item); } for (ZhihuStoriesEntity enti : entity.getTop_stories()) { EssayListAdapter.MultiItem item = new EssayListAdapter.MultiItem(enti, TYPE_BASE); list.add(item); } mAdapter.replaceData(list); getLifecycle().addObserver(new LifecycleObserver() { @Override public int hashCode() { return super.hashCode(); } }); }
Adapter主要是将数据安置在一个个Item里
public static class MultiItem implements MultiItemEntity { public final static int TYPE_BASE = 1; public IEssayItem data; public int type; public void update(Context cxt, final BaseViewHolder helper) { switch (type) { case TYPE_BASE: helper.setText(R.id.item_title, data.getTitle()); helper.setText(R.id.item_time, data.getDate()); helper.setText(R.id.item_from, data.getAuthor()); Glide.with(cxt) .load(data.getImageUrl()) .centerCrop() .into(new SimpleTarget() { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> glideAnimation) { helper.setImageDrawable(R.id.icon_item, resource); } }); break; } } public MultiItem(IEssayItem d, int type) { data = d; this.type = type; } @Override public int getItemType() { return type; } }
3.EssayViewModel是数据的容器,在这里从各个数据来源获取数据,这个数据可以来自于网络,可以来自数据库缓存等等,他的作用就是将从各个数据源获取的数据转换成app需要的数据,也就是类对象,不过我们通常使用一个Repository去进行一层封装,我们在Repository中进行网络请求,缓存等等有关数据的操作,然后将处理好的数据暴露给ViewModel,这样ViewModel的职责就仅仅是数据的一个容器,并不进行数据处理,也不知道数据的来源,只需要提供让UI获取正确数据的接口就可以了;
package win.canking.mvvmarch.module_essay.viewmodel; import android.app.Application; import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MediatorLiveData; import android.arch.lifecycle.Observer; import android.support.annotation.Nullable; import win.canking.mvvmarch.architecture.Resource; import win.canking.mvvmarch.module_essay.db.entity.ZhihuItemEntity; import win.canking.mvvmarch.module_essay.repsitory.EssayRepository; /** * Created by changxing on 2017/12/4. */ public class EssayViewModel extends AndroidViewModel { private EssayRepository mRepository; private MediatorLiveData> mCache; public EssayViewModel(Application app) { super(app); mRepository = new EssayRepository(app); } public LiveData > getEssayData() { if (mCache == null) { mCache = mRepository.loadEssayData(); } return mCache; } public void updateCache() { final LiveData > update = mRepository.update(); mCache.addSource(update, new Observer >() { @Override public void onChanged(@Nullable Resource zhihuItemEntityResource) { mCache.setValue(zhihuItemEntityResource); } }); } public void addMore() { //TODO: 加载更多 } }
4.EssayRepository 在这里进行网络请求,或者有缓存那么就读缓存,然后将这些数据进行对应的处理,该缓存的缓存,该封装的封装,
public MediatorLiveData> loadEssayData() { return new AbsDataSource , ZhihuItemEntity>() { @Override protected void saveCallResult(@NonNull ZhihuItemEntity item) { zhuhuDao.insertItem(item); } @Override protected boolean shouldFetch(@Nullable ZhihuItemEntity data) { //TODO:Realize your own logic return true; } @NonNull @Override protected LiveData loadFromDb() { LiveData entity = zhuhuDao.loadZhuhu(); return entity; } @NonNull @Override protected LiveData > createCall() { final MediatorLiveData > result = new MediatorLiveData<>(); executor.networkIO().execute(new Runnable() { @Override public void run() { try { LiveData > netGet = webService.getEssay(DAY); result.addSource(netGet, new Observer >() { @Override public void onChanged(@Nullable IRequestApi essayDayEntityIRequestApi) { result.postValue(essayDayEntityIRequestApi); } }); } catch (Exception e) { e.printStackTrace(); onFetchFailed(); } } }); return result; } @Override protected void onFetchFailed() { //TODO: update the UI } }.getAsLiveData(); }
这里有两点:一个是MediatorLiveData类,这个类继承自LiveData,LiveData是一个数据持有类,是一款基于观察者模式的可感知生命周期的核心组件。LiveData 为界面代码 (Observer)的监视对象 (Observable),当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。通过 LiveData,开发者可以方便地构建安全性更高、性能更好的高响应度用户界面,而MediatorLiveData拥有一个addSource()方法,它可以用来正确的处理其他多个LiveData的事件变化,并处理这些事件
第二点就是AbsDataSource主要是对数据源的一个封装
package win.canking.mvvmarch.architecture; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MediatorLiveData; import android.arch.lifecycle.Observer; import android.os.AsyncTask; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; /** * Created by changxing on 2017/12/3. */ public abstract class AbsDataSource<ResultType, RequestType> { private final MediatorLiveDataResultType>> result = new MediatorLiveData<>(); @WorkerThread protected abstract void saveCallResult(@NonNull RequestType item); @MainThread protected abstract boolean shouldFetch(@Nullable ResultType data); // Called to get the cached getDate from the database @NonNull @MainThread protected abstract LiveData<ResultType> loadFromDb(); @NonNull @MainThread protected abstract LiveData RequestType>> createCall(); @MainThread protected abstract void onFetchFailed(); @MainThread public AbsDataSource() { final LiveData<ResultType> dbSource = loadFromDb(); result.setValue(Resource.loading(dbSource.getValue(),"db load")); result.addSource(dbSource, new Observer<ResultType>() { @Override public void onChanged(@Nullable ResultType resultType) { result.removeSource(dbSource); if (shouldFetch(resultType)) { fetchFromNetwork(dbSource); } else { result.addSource(dbSource, new Observer<ResultType>() { @Override public void onChanged(@Nullable ResultType resultType) { result.setValue(Resource.success(resultType)); } }); } } }); } private void fetchFromNetwork(final LiveData<ResultType> dbSource) { final LiveData RequestType>> apiResponse = createCall(); result.addSource(apiResponse, new Observer RequestType>>() { @Override public void onChanged(@Nullable final IRequestApi<RequestType> requestTypeRequestApi) { result.removeSource(apiResponse); //noinspection ConstantConditions if (requestTypeRequestApi.isSuccessful()) { saveResultAndReInit(requestTypeRequestApi); } else { onFetchFailed(); result.addSource(dbSource, new Observer<ResultType>() { @Override public void onChanged(@Nullable ResultType resultType) { result.setValue( Resource.error(requestTypeRequestApi.getErrorMsg(), resultType)); } }); } } }); } @MainThread private void saveResultAndReInit(final IRequestApi<RequestType> response) { new AsyncTask , Void, Void>() { @Override protected Void doInBackground(Void... voids) { saveCallResult(response.getBody()); return null; } @Override protected void onPostExecute(Void aVoid) { // we specially request a new live getDate, // otherwise we will get immediately last cached value, // which may not be updated with latest results received from network. result.addSource(loadFromDb(), new Observer<ResultType>() { @Override public void onChanged(@Nullable ResultType resultType) { result.setValue(Resource.success(resultType)); } }); } }.execute(); } public final MediatorLiveData ResultType>> getAsLiveData() { return result; } }
5.网络请求数据已经说过了,接下来说一说Room数据库框架,在AbsDataSource的构造方法中,首先判断是否应该从网络中获取数据,当从网络获取失败时,应该从数据库中获取;Room数据库使用:
(1)在app的build文件中导入
defaultConfig { applicationId "win.canking.mvvmarch" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } }
这样就可以将配置好的数据库结构导出到文件中,将会以json格式输出
(2)根据网络请求后的json数据建立实体类
package win.canking.mvvmarch.module_essay.db.entity; import android.arch.persistence.room.Entity; import android.arch.persistence.room.PrimaryKey; import java.util.List; @Entity(tableName = "zhuhulist") public class ZhihuItemEntity { @PrimaryKey(autoGenerate = true) private int id; public String date; public Liststories; public List top_stories; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public List getStories() { return stories; } public void setStories(List stories) { this.stories = stories; } public List getTop_stories() { return top_stories; } public void setTop_stories(List top_stories) { this.top_stories = top_stories; } }
@Entity指定数据库表 @PrimaryKey指定表主键
(2)dao层(暴露查询数据库方法的接口,ide会自动创建接口的实现类zhuhuDao_Impl)
package win.canking.mvvmarch.module_essay.db.dao; import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.Query; import win.canking.mvvmarch.module_essay.db.entity.ZhihuItemEntity; @Dao public interface ZhuhuDao { @Query("SELECT * FROM zhuhulist order by id desc, id limit 0,1") LiveDataloadZhuhu(); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertItem(ZhihuItemEntity products); }
(3)数据转换类 (将Date转换为long型存储到数据库中,取数据时,再将long型转换为Date)
package win.canking.mvvmarch.db_holder.converter; import android.arch.persistence.room.TypeConverter; import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.json.JSONArray; import org.json.JSONException; import java.util.ArrayList; import java.util.Date; import java.util.List; import win.canking.mvvmarch.module_essay.db.entity.ZhihuStoriesEntity; public class DateConverter { @TypeConverter public static Date toDate(Long timestamp) { return timestamp == null ? null : new Date(timestamp); } @TypeConverter public static Long toTimestamp(Date date) { return date == null ? null : date.getTime(); } @TypeConverter public static List<ZhihuStoriesEntity> toString(String timestamp) { List<ZhihuStoriesEntity> tmp = new ArrayList<>(); Gson gson = new GsonBuilder().create(); try { JSONArray jsonArray = new JSONArray(timestamp); for (int i = 0, j = jsonArray.length(); i < j; i++) { ZhihuStoriesEntity entity = gson.fromJson(jsonArray.getJSONObject(i).toString(), ZhihuStoriesEntity.class); tmp.add(entity); } } catch (JSONException e) { } return tmp; } @TypeConverter public static String toZhihuStoriesEntity(List<ZhihuStoriesEntity> list) { StringBuffer res = new StringBuffer("["); Gson gson = new GsonBuilder().create(); try { for (ZhihuStoriesEntity e : list) { res.append(gson.toJson(e) + ","); } } catch (Exception e) { } if (res.length() > 1) { res.replace(res.length() - 1, res.length(), "]"); } else { res.append("]"); } Log.d("changxing", res.toString()); return res.toString(); } }
(4)Room数据库管理类(创建数据库,更新数据库)
package com.example.pengganggui.lvrtest2.db_holder; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.room.Database; import android.arch.persistence.room.Room; import android.arch.persistence.room.RoomDatabase; import android.arch.persistence.room.TypeConverters; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import com.example.pengganggui.lvrtest2.AppExecutors; import com.example.pengganggui.lvrtest2.db_holder.converter.DateConverter; import com.example.pengganggui.lvrtest2.domain.DbCallbackHelper; import com.example.pengganggui.lvrtest2.module_essay.db.dao.EssayDao; import com.example.pengganggui.lvrtest2.module_essay.db.dao.ZhuhuDao; import com.example.pengganggui.lvrtest2.module_essay.db.entity.EssayDayEntity; import com.example.pengganggui.lvrtest2.module_essay.db.entity.ZhihuItemEntity; /** * Created by pengganggui on 2018/7/14. * 数据库实体类 */ @Database(entities ={EssayDayEntity.class, ZhihuItemEntity.class},version = 1) @TypeConverters(DateConverter.class) public abstract class AppDB extends RoomDatabase { private static AppDB sInstance; @VisibleForTesting public static final String DATABASE_NAME="canking.db"; public abstract EssayDao essayDao(); public abstract ZhuhuDao zhuhuDao(); private final MutableLiveDatamIsDatabaseCreated=new MutableLiveData<>(); public static AppDB getsInstance(final Context context, final AppExecutors executors){ if (sInstance==null){ synchronized (AppDB.class){ if (sInstance==null){ sInstance=buildDatabase(context.getApplicationContext(),executors); sInstance.updateDatabaseCreated(context.getApplicationContext()); } } } return sInstance; } private void updateDatabaseCreated(Context applicationContext) { if (applicationContext.getDatabasePath(DATABASE_NAME).exists()){ setDatabaseCreated(); } } private static AppDB buildDatabase(final Context applicationContext, final AppExecutors executors) { return Room.databaseBuilder(applicationContext,AppDB.class,DATABASE_NAME) .addCallback(new Callback() { @Override public void onCreate(@NonNull final SupportSQLiteDatabase db) { super.onCreate(db); executors.diskIO().execute(new Runnable() { @Override public void run() { addDelay(); AppDB database=AppDB.getsInstance(applicationContext,executors); DbCallbackHelper.dispatchOnCreate(db); database.setDatabaseCreated(); } }); } }).addMigrations(DbCallbackHelper.getUpdateConfig()).build(); } private void setDatabaseCreated() { mIsDatabaseCreated.postValue(true); } private static void addDelay() { try{ Thread.sleep(4000); }catch (InterruptedException ignored){ } } public LiveData getDatabaseCreated(){ return mIsDatabaseCreated; } }
这样就可以搭建一个LVR框架的项目;