Android架构组件


好处

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

框架中包含的组件

  • 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设计模式是对一个表中数据增,删,查,改的操作

@Daopublic 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. */@Deprecatedpublic 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

更多相关文章

  1. 你不知道的 Android(安卓)WebView 使用漏洞
  2. android:服务器与客户端的双向开发
  3. adb connect远程Android报错:refused,拒绝连接(10061)
  4. Android常用的数据结构
  5. Android(安卓)蓝牙BLE开发详解
  6. 说说 Android(安卓)的内容提供器(ContentResolver )
  7. android第一次启动时Settings的默认值
  8. 传感器概念
  9. Android之组件详解

随机推荐

  1. Android(安卓)TextView 实现跑马灯效果
  2. android ndk实现java层代码。。
  3. ADB和Fastboot的谷歌官方下载链接
  4. android binder c++层-客户端(c++) 调用
  5. android 学习笔记
  6. Android获取SD卡视频音频文件
  7. Android(安卓)app的音视频播放功能
  8. Android(安卓)判断当前网络 wifi ctwap(c
  9. Android(安卓)Sqlite数据库中判断某个表
  10. android 监听短信和来电