android开发模式LiveData+ViewModel+Room+Retrofit

导依赖

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> LiveData<IRequestApi<ResultType>> getEssay(@EssayWebService.EssayType String type) throws IOException {
        EssayWebService api = mRetrofit.create(EssayWebService.class);

        Call<ZhihuItemEntity> essayCall = api.getZhihuList("latest");
        MediatorLiveData<IRequestApi<ResultType>> result = new MediatorLiveData<>();
        final Response<ZhihuItemEntity> 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<Resource<ZhihuItemEntity>>() {
        @Override
        public void onChanged(@Nullable Resource<ZhihuItemEntity> 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<EssayListAdapter.MultiItem> 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<GlideDrawable>() {
                            @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获取正确数据的接口就可以了;

扫描二维码关注公众号,回复: 2257052 查看本文章

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<Resource<ZhihuItemEntity>> mCache;

    public EssayViewModel(Application app) {
        super(app);
        mRepository = new EssayRepository(app);
    }

    public LiveData<Resource<ZhihuItemEntity>> getEssayData() {
        if (mCache == null) {
            mCache = mRepository.loadEssayData();
        }
        return mCache;
    }

    public void updateCache() {
        final LiveData<Resource<ZhihuItemEntity>> update = mRepository.update();
        mCache.addSource(update, new Observer<Resource<ZhihuItemEntity>>() {
            @Override
            public void onChanged(@Nullable Resource<ZhihuItemEntity> zhihuItemEntityResource) {
                mCache.setValue(zhihuItemEntityResource);
            }
        });

    }

    public void addMore() {
        //TODO: 加载更多
    }

}

4.EssayRepository  在这里进行网络请求,或者有缓存那么就读缓存,然后将这些数据进行对应的处理,该缓存的缓存,该封装的封装,

public MediatorLiveData<Resource<ZhihuItemEntity>> loadEssayData() {
    return new AbsDataSource<ZhihuItemEntity, 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<ZhihuItemEntity> loadFromDb() {
            LiveData<ZhihuItemEntity> entity = zhuhuDao.loadZhuhu();
            return entity;
        }

        @NonNull
        @Override
        protected LiveData<IRequestApi<ZhihuItemEntity>> createCall() {
            final MediatorLiveData<IRequestApi<ZhihuItemEntity>> result = new MediatorLiveData<>();

            executor.networkIO().execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        LiveData<IRequestApi<ZhihuItemEntity>> netGet = webService.getEssay(DAY);
                        result.addSource(netGet, new Observer<IRequestApi<ZhihuItemEntity>>() {
                            @Override
                            public void onChanged(@Nullable IRequestApi<ZhihuItemEntity> 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 MediatorLiveData<Resource<ResultType>> 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<IRequestApi<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<IRequestApi<RequestType>> apiResponse = createCall();

        result.addSource(apiResponse, new Observer<IRequestApi<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, 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<Resource<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 List<ZhihuStoriesEntity> stories;
    public List<ZhihuStoriesEntity> 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<ZhihuStoriesEntity> getStories() {
        return stories;
    }

    public void setStories(List<ZhihuStoriesEntity> stories) {
        this.stories = stories;
    }

    public List<ZhihuStoriesEntity> getTop_stories() {
        return top_stories;
    }

    public void setTop_stories(List<ZhihuStoriesEntity> 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")
    LiveData<ZhihuItemEntity> 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<Boolean> 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<Boolean> getDatabaseCreated(){
        return mIsDatabaseCreated;
    }

}

这样就可以搭建一个LVR框架的项目;

猜你喜欢

转载自blog.csdn.net/pgg_cold/article/details/81043143