导依赖

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> LiveDataResultType>> getEssay(@EssayWebService.EssayType String type) throws IOException {        EssayWebService api = mRetrofit.create(EssayWebService.class);        Call essayCall = api.getZhihuList("latest");        MediatorLiveDataResultType>> 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;    }}
2.MainActivity中逻辑没什么,真正的UI界面时EssayFragment,主要逻辑是:

initView()主要初始化ViewModel,adapter,和各个控件

subscribeUi()方法主要将数据绑定给UI界面,使用viewmodel从数据库中查询获取数据,

private void subscribeUi() {    viewModel.getEssayData().observe(this, new Observer>() {        @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);        }    });}
updateUI()主要将数据传输给Adapter

private void updateUI(@NonNull ZhihuItemEntity entity) {    List list = 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 LiveDataRequestType>> 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 LiveDataRequestType>> apiResponse = createCall();        result.addSource(apiResponse, new ObserverRequestType>>() {            @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 MediatorLiveDataResultType>> 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 List stories;    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;@Daopublic interface ZhuhuDao {    @Query("SELECT * FROM zhuhulist  order by id desc, id limit 0,1")    LiveData loadZhuhu();    @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 MutableLiveData mIsDatabaseCreated=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框架的项目;

更多相关文章

  1. Android与服务器端数据交互(基于SOAP协议整合android+webservice)
  2. android 数据库更改数据库位置【DbFlow示范】
  3. android分组数据适配器demo
  4. Android jetpack Room数据库(一)基本使用
  5. Android ListView 滚动加载数据
  6. Android上解析Json格式数据
  7. 【Android】SQLite数据库的简单使用
  8. Android 定时到服务器取数据并刷新
  9. android 客户端 smtp 协议发送数据

随机推荐

  1. Android(安卓)SQLite使用方法
  2. android camera研究
  3. Android向服务器传接和接收数据的方法汇
  4. Android的消息机制
  5. android socket通信(下)
  6. 收藏Android学习相关资料
  7. 专题分纲目录 android 系统核心机制 bind
  8. FMOD在Android玩音响系统的抖动问题
  9. Android(安卓)Touch事件传递机制引发的血
  10. Android手机屏幕的三种状态