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

Android架构组件
Android|Android Acrchitecture Components( 架构组件)+热门框架(Retrofit+OkHttp+RxJava2+Glide)
文章图片

好处:

  • 存储数据
  • 管理生命周期
  • 模块化
  • 避免常见的错误
  • 减少样板代码
框架中包含的组件:
  • 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, "开始获取电影数据"); returnsearDB(); } private Flowable> searDB(){ return movieDataSource.getMovieList().flatMap( list -> { Log.i(tag, "数据库中获取"); if (list==null||list.size()==0){ returnsearchNet(); }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); returnnew LocalMovieDataSource(database.movieDao()); } /** * 创建ViewModelFactory * @param context * @return */ public static ViewModelFactory providerViewModelFactory(Context context){ MovieDataSource dataSource=provideMovieDataSource(context); RetrofitClient retrofitClient=providerRetrofitClient(); returnnew 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 extendsAppCompatActivity 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(); } }

运行效果如下: 【Android|Android Acrchitecture Components( 架构组件)+热门框架(Retrofit+OkHttp+RxJava2+Glide)】
项目链接:https://github.com/13767004362/ArchitectureComponentsDemo

    推荐阅读