Android Acrchitecture Components( 架构组件)+热门框架(Retrofit+OkHttp+RxJava2+Glide)

Android架构组件


Android Acrchitecture Components( 架构组件)+热门框架(Retrofit+OkHttp+RxJava2+Glide)_第1张图片

好处

  • 存储数据
  • 管理生命周期
  • 模块化
  • 避免常见的错误
  • 减少样板代码

框架中包含的组件

  • Room

  • ViewModel

  • LiveData

  • LifecycleObserver和LiecycleOwner

1. Room介绍

一个稳健的SQL对象映射库

2.LiveData 介绍:

是一种可观测数据容器。它会在数据变化时通知外观测器,以便于更新界面。它还具备生命周期感知能力,例如:一个Activity何时离开屏幕或者销毁

3. LifecycleObserver和LiecycleOwner介绍 :

LifecycleOwner是具备声明周期的对象,可以是Activity和Fragment.

LifecycleObserver用于观测LifecycleOwner , 且在LifecycleOwner的生命周期变化时候,收到通知。

观测过程

界面组件–>LiveData–>LifecycleOwner(Activity和Fragment).

配置变更的处理

系统内存不足导致当Activity被销毁后又重建,或者手机屏幕旋转导致Activity销毁后重建的情况,若是LiveData与Activity生命周期捆绑会导致多次重复查询数据,和一些不必要的代码。

因此,将LiveData绑定和一些与界面有关的数据放到LiveModel中。

4. ViewModel介绍

ViewModel是为了界面组件提供数据并可在配置变更后继续存在的对象。


实战案例


通过一个在线获取网络电影资源,离线加载数据库的电影资源的案例,加深对Android 架构组件的了解。

需求分析

一个显示张艺谋电影的列表界面。先从数据库中获取数据,若是获取为空,则通过网络,从豆瓣API中搜索张艺谋的电影列表,最终显示列表上。

本案例中框架和类库选取

1. Android 架构组件:

  • Room数据库

  • ViewModel

  • LiveData

  • Lifecycles

2. 网络通讯库:

  • Rretrofit
  • OkHttp

3. 图片异步处理库:

  • Glide

4. 异步线程通讯库:

  • RxJava2
  • RxAndroid

5. UI控件

  • RecyclerView
  • SwipeRefreshLayout

前期准备


1.添加google maven repository:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}

在Project的builde.gralde中添加google maven repository。

2. 类库依赖

根据上面的选取,在Module的builde.gradle中添加各种依赖:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
    testCompile 'junit:junit:4.12'
    /**************    Architecture components库  *************/
    //Lifecycles库
    compile 'android.arch.lifecycle:runtime:1.0.3'
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
    //ViewModel和 LiveData库
    compile 'android.arch.lifecycle:extensions:1.0.0'
    //Room库
    compile 'android.arch.persistence.room:runtime:1.0.0'
    compile "android.arch.persistence.room:rxjava2:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

    /**************    RxJava库和RxAndroid库 *************/
    compile 'io.reactivex.rxjava2:rxjava:2.1.1'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    /**************    Retrofit 和OkHttp 库  *************/
    //异步加载图像
    compile 'com.github.bumptech.glide:glide:3.8.0'
    //网络请求操作
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.okhttp3:okhttp:3.8.0'
    //解析数据,Gson方式json
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    //网络日志输出
    compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
    //结合RxJava2使用
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
}

3. 添加Java8语法支持

在项目中集成retrolambda插件 , 在在Module的builde.gradle中添加以下代码:

apply plugin: 'com.android.application'
//gradle-retrolambda配置
apply plugin: 'me.tatarka.retrolambda'
android {

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

更多详情,请阅读AndroidStudio 中开启Java8语法和Retrolambda库的使用。

4. 配置Room数据库中Schema export位置

android {

    defaultConfig {

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }

}

更多详情,请阅读Android 架构组件之Room数据库 处理Schema export Error。

对了,别忘记在AndroidManifest.xml中添加网络权限.

撸起袖子,编码实战


1. 编写Retrofit和OkHttp配置

网络资源:https://api.douban.com/v2/movie/search?q=张艺谋

创建一个Retrofit的单例使用类,且初始化其配置:

public class RetrofitClient {
    private final String BASE_URL = "https://api.douban.com/v2/movie/";
    private final Retrofit retrofit;
    private static RetrofitClient instance;
    private DouBanService service;
    private RetrofitClient(){
       OkHttpClient okHttpClient = OkHttpProvider.createOkHttpClient();
        this.retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();
        this.service=retrofit.create(DouBanService.class);
    }
    public synchronized static RetrofitClient getInstance(){
        if (instance==null){
            instance=new RetrofitClient();
        }
        return instance;
    }

    public Flowable getMovieList(){
        String url = "search";
        Map map=new HashMap<>();
        map.put("q","张艺谋");
        return this.service.movieList(url,map);
    }
}

为OkHttp配置一个日志拦截器,方便查看请求和响应:

public class OkHttpProvider {
    /**
     * 自定义配置OkHttpClient
     * @return
     */
    public static OkHttpClient createOkHttpClient(){
        OkHttpClient.Builder builder=new OkHttpClient.Builder();
        HttpLoggingInterceptor loggingInterceptor=new HttpLoggingInterceptor();
        //打印一次请求的全部信息
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(loggingInterceptor);
        return builder.build();
    }
}

返回数据的实体类:

public class MovieBeanList {
    public List getSubjects() {
        return subjects;
    }
    private List subjects;
}

这里采用Rtrofit和Room数据库共享一个MovieBean,这个实体类由来下面介绍。

2. 编写Glide配置:创建圆形图片

为了实现显示圆形的图片,为Glide创建一个圆形图片的BitmapImageViewTarget子类。

public class CircularBitmapImageViewTarget extends BitmapImageViewTarget {
    private Context context;
    private ImageView imageView;
    public CircularBitmapImageViewTarget(Context context,ImageView view) {
        super(view);
        this.context=context;
        this.imageView=view;
    }
    /**
     * 重写 setResource(),生成圆角的图片
     * @param resource
     */
    @Override
    protected void setResource(Bitmap resource) {
        RoundedBitmapDrawable bitmapDrawable= RoundedBitmapDrawableFactory.create(this.context.getResources(),resource);
        bitmapDrawable.setCircular(true);
        this.imageView.setImageDrawable(bitmapDrawable);
    }
}

3. 编写Room数据库

Room数据库是一个稳健的SQL对象映射库,将实体和表一一映射。

先来创建表名和字段名

创建一个实体,编写属性(该名与表中字段名一样)。

@Entity(tableName = "movies")
public class MovieBean {
    /**
     * @PrimaryKey设置为主键,
     * 且设置autoGenerate为true,自增长
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int id;
    @ColumnInfo(name = "year")
    private String year;
    @ColumnInfo(name = "title")
    private String title;
    @ColumnInfo(name = "image")
    private String image;
    @Ignore
    private Images images;

    public MovieBean(String year, String title, String image) {
        this.year = year;
        this.title = title;
        this.image = image;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getYear() {
        return year;
    }
    public void setYear(String year) {
        this.year = year;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }

    public Images getImages() {
        return images;
    }

    public void setImages(Images images) {
        this.images = images;
    }
    /**
     * 网络数据源对应的实体类
     */
    public static class Images{
        private String small;

        public String getSmall() {
            return small;
        }

        public void setSmall(String small) {
            this.small = small;
        }

        public String getLarge() {
            return large;
        }

        public void setLarge(String large) {
            this.large = large;
        }

        private String large;
    }
}

由上面可知

  • 表名的设置:@Entity注解,设置表名

    @Entity(tableName = "movies")
    public class MovieBean {
    
    }
  • 设置主键和自增长的字段

    /**
     * @PrimaryKey设置为主键,
     * 且设置autoGenerate为true,自增长
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int id;
  • 设置剩余字段:@ColumnInfo注解指定表中的字段名

    @ColumnInfo(name = "year")
    private String year;
    @ColumnInfo(name = "title")
    private String title;
    @ColumnInfo(name = "image")
    private String image;
    
  • 若是实体类中某些属性不设置为字段名,可忽略:

    @Ignore
    private Images images;

接下来,编写DAO层

DAO设计模式是对一个表中数据增,删,查,改的操作

@Dao
public interface MovieDao {
    /**
     * 查询movies表中全部数据,且返回RxJava2 中Flowable对象
     * @return
     */
    @Query("select * from movies")
    Flowable> getMovieList();

    /**
     * 插入全部数据
     */
    @Insert
    void insertMovieList(List movieBeans);
    /**
     * 删除movies表中全部数据
     */
    @Query("delete from movies")
    void deleteAllMovies();
}

从以上代码可知

  • 定义DAO接口:@Dao注解标注接口

  • 查询的SQL:@Query注解内添加SQL

  • 插入的SQL:使用@Insert注解

注意点:原本Room数据库的查询可以返回LiveDta,因这里结合RxJava2使用,直接返回Flowable对象

创建数据库,设置数据库名和数据库版本

@Database(entities = {MovieBean.class},version = 1)
public abstract class MovieDatabase extends RoomDatabase {
    /**
     * 获取Dao对象
     *
     * @return
     */
    public abstract MovieDao movieDao();

    /**
     * 单例类MovieDatabase,同步获取对象
     */
    private static volatile MovieDatabase instance;

    public static MovieDatabase getInstance(Context context) {
        if (instance == null) {
            synchronized (MovieDatabase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(), MovieDatabase.class, "movies.db").build();
                }
            }
        }
        return instance;
    }
}

从以上代码可知

  • @Database注解指定数据库表中映射的实体和版本。
  • 指定DAO接口和数据库文件名

接下来,创建一个数据库的操作类

操作类通过调用DAO对象中的方法,从而操作数据库,实现数据的增删查改。

先抽象出一个行为的接口:

public interface MovieDataSource {
    Flowable> getMovieList();
    void insertMovieList(List movieBeans);
    void deleteAllMovies();
}

继续撸代码,编写MovieDataSource接口的具体的实现类,和实现逻辑

public class LocalMovieDataSource implements MovieDataSource {
   private MovieDao movieDao;
    public LocalMovieDataSource(MovieDao movieDao) {
        this.movieDao = movieDao;
    }
    @Override
    public Flowable> getMovieList() {
        return movieDao.getMovieList();
    }
    @Override
    public void insertMovieList(List movieBeans) {
        movieDao.insertMovieList(movieBeans);
    }
    @Override
    public void deleteAllMovies() {
        movieDao.deleteAllMovies();
    }
}

4. 编写ViewModel:

定义一个ViewModelProvider.Factory子类

用于创建ViewModel对象,且提供数据库操作类和网路操作类。

public class ViewModelFactory implements ViewModelProvider.Factory {
    private final MovieDataSource movieDataSource;
    private final RetrofitClient retrofitClient;
    public ViewModelFactory(MovieDataSource movieDataSource,RetrofitClient retrofitClient) {
        this.movieDataSource = movieDataSource;
        this.retrofitClient=retrofitClient;
    }
    @NonNull
    @Override
    public  T create(@NonNull Class modelClass) {
        if (modelClass.isAssignableFrom(MovieViewModel.class)){
            return (T) new MovieViewModel(movieDataSource,retrofitClient);
        }
        return null;
    }
}

定义与Activity对应的ViewModel子类

用于管控Activity中使用到的数据。

public class MovieViewModel extends ViewModel {
    private MovieDataSource movieDataSource;
    private RetrofitClient retrofitClient;
    private List movieBeanList;
    private final String tag = MovieViewModel.class.getSimpleName();
    public MovieViewModel(MovieDataSource movieDataSource, RetrofitClient retrofitClient) {
        this.movieDataSource = movieDataSource;
        this.retrofitClient = retrofitClient;
    }
    /**
     * 采用数据库,磁盘逐渐获取
     *
     * @return
     */
    public Flowable> getMovieList() {
        Log.i(tag, "开始获取电影数据");
        return  searDB();
    }
    private Flowable> searDB(){
        return movieDataSource.getMovieList().flatMap( list -> {
            Log.i(tag, "数据库中获取");
            if (list==null||list.size()==0){
                return  searchNet();
            }else{
                movieBeanList=list;
            }
            return Flowable.just(list);
        });
    }
    /**
     * 若是数据库中无,则从网络中获取
     * 最后,写入数据库
     *
     * @return
     */
    private Flowable> searchNet() {
        return retrofitClient.getMovieList().flatMap(movieBeen -> {
            Log.i(tag, "网络中获取");
            if (movieBeen.getSubjects().size() > 0) {
                movieBeanList = movieBeen.getSubjects();
                for (MovieBean bean : movieBeen.getSubjects()) {
                    bean.setImage(bean.getImages().getLarge());
                }
                movieDataSource.insertMovieList(movieBeen.getSubjects());
            }
            return Flowable.just(movieBeen.getSubjects());
        });
    }
}

5. 编写lifecycle

LifecycleActivity类的源码:

/**
 * @deprecated Use {@code android.support.v7.app.AppCompatActivity} instead of this class.
 */
@Deprecated
public class LifecycleActivity extends FragmentActivity {
}

继承LifecycleActivity类,结果却发觉已经废弃了。根据建议改为,继承AppCompatActivity。

6. 工厂模式创建对象

public class AppFactory {
    /**
     *创建MovieDataSource 对象
     * @param context
     * @return
     */
    public static MovieDataSource provideMovieDataSource(Context context){
        MovieDatabase database=MovieDatabase.getInstance(context);
        return  new LocalMovieDataSource(database.movieDao());
    }
    /**
     * 创建ViewModelFactory
     * @param context
     * @return
     */
    public static ViewModelFactory providerViewModelFactory(Context context){
        MovieDataSource dataSource=provideMovieDataSource(context);
        RetrofitClient retrofitClient=providerRetrofitClient();
        return  new ViewModelFactory(dataSource,retrofitClient);
    }
    /**
     * 创建Retrofit单例类
     * @return
     */
    public static RetrofitClient providerRetrofitClient(){
        return RetrofitClient.getInstance();
    }
}

7. 处理UI显示

采用SwipeRefreshLayout刷新,RecyclerView显示电影列表。

定义一个支持非直接滚动视图的SwipeRefreshLayout

public class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout {
    private View scrollUpChild;
    public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    /**
     * 设置在哪个view中触发刷新。
     * @param view
     */
    public void setScrollUpChild(View view){
        this.scrollUpChild=view;
    }

    /**
     *ViewCompat..canScrollVertically():用于检查view是否可以在某个方向上垂直滑动
     * @return
     */
    @Override
    public boolean canChildScrollUp() {
        if(scrollUpChild!=null){
            return ViewCompat.canScrollVertically(scrollUpChild,-1);
        }
        return super.canChildScrollUp();
    }
    /**
     * 设置
     * @param indicator
     */
    public void showIndicator(boolean indicator){
        this.post(() -> this.setRefreshing(indicator));
    }

    /**
     * 设置默认颜色
     */
    public void setDefalutColor(){
        this.setColorSchemeColors(Color.parseColor("#263238"), Color.parseColor("#ffffff"), Color.parseColor("#455A64"));
    }
}

RecyclerView的设置比较简单,这列省略。

最后,在界面上直接开启加载,获取电影数据,然后显示在视图中:

public class MainActivity extends  AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
    private final CompositeDisposable compositeDisposable = new CompositeDisposable();
    private ViewModelFactory viewModelFactory;
    private MovieViewModel movieViewModel;
    private RecyclerView recyclerView;
    private ScrollChildSwipeRefreshLayout refreshLayout;
    private MovieListAdapter movieListAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        initView();
    }
    /***
     * 初始化控件
     */
    private void initView() {
        this.recyclerView = findViewById(R.id.main_recyclerView);
        this.recyclerView.setLayoutManager(new LinearLayoutManager(this));
        this.movieListAdapter = new MovieListAdapter();
        this.recyclerView.setAdapter(this.movieListAdapter);
        this.refreshLayout = findViewById(R.id.main_swipeRefreshLayout);
        this.refreshLayout.setDefalutColor();
        this.refreshLayout.showIndicator(true);
        this.refreshLayout.setScrollUpChild(recyclerView);
        this.refreshLayout.setOnRefreshListener(this);
        this.onRefresh();
    }
    /**
     * 初始化
     */
    private void init() {
        this.viewModelFactory = AppFactory.providerViewModelFactory(this);
        this.movieViewModel = ViewModelProviders.of(this, this.viewModelFactory).get(MovieViewModel.class);
    }
    /**
     * 加载数据
     */
    private void loadData() {
        Disposable disposable = this.movieViewModel.getMovieList()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(movieBeen -> {
                            this.movieListAdapter.changeData(movieBeen);
                            this.refreshLayout.showIndicator(false);
                        }, error -> {
                            this.refreshLayout.showIndicator(false);
                            showToast(error.getMessage());
                        },()->{
                         showToast("执行完成");
                        }
                );
        this.compositeDisposable.add(disposable);
    }
    /**
     * Toast弹窗提示
     *
     * @param content
     */
    private void showToast(String content) {
        Toast.makeText(getApplicationContext(), content, Toast.LENGTH_SHORT).show();
    }
    @Override
    protected void onStop() {
        super.onStop();
        compositeDisposable.clear();
    }
    @Override
    public void onRefresh() {
        loadData();
    }
}

运行效果如下

项目链接:https://github.com/13767004362/ArchitectureComponentsDemo

你可能感兴趣的:(Android,项目架构(MVP模式,组件化,架构组件,插件化))