Android在2018年的Google大会推出了Android JetPack工具库,这个工具库将常用的功能统一封装起来,提高开发者的开发效率,提高代码的复用性。目前JetPack主要封装的功能包含基础框架、应用架构、应用行为和UI界面四个方面,除了应用架构比较复杂外其他的使用起来都相对简单,这里主要介绍JetPack的架构组件功能。
Android的数据库是基于SQLite数据库的简单封装,用户需要继承SQLiteOpenHelper类实现数据的增删改查,手动转换关系型数据到Java对象数据的操作繁琐而且很容易产生大量的重复代码。目前市面上有不少第三方开源的ORM库,不过这些库的体积庞大,有些还使用反射容易造成性能问题。
Room ORM框架基于注解和APT在编译时生成代码,用户只需要简单配置实体对象就能够正确生成数据库表,所有数据库操作都只需要用户提供对应的SQL语句,查询工作完全由框架生成模板代码。ROOM框架封装后的数据库逻辑完全是面向对象的实现方式,能够轻松的集成到Android开发项目中。
implementation "android.arch.persistence.room:runtime:$rootProject.roomVersion"
annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"
androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion“
Room.databaseBuilder(sContext, AdsDatabase.class, "ads_database.db")
.addCallback(new Callback)
.addMigrations(new Migration(1, 2) )
.allowMainThreadQueries()
.build();
@Entity(tableName = "tb_download")
public class DownloadEntity {
@PrimaryKey(autoGenerate = true)
private int id;
private String url;
private long startTime;
private long downloadTime;
private int status;
private int loadType;
private String description;
}
@Dao
public interface DownloadDao {
@Insert
void insert(DownloadEntity entity);
@Query("delete from tb_download")
void deleteAll();
@Query("select * from tb_download where status in (:status)")
List queryByStatus(int[] status);
}
@Database(entities = { DownloadEntity.class, MovieEntity.class }, version = 3)
public abstract class AdsDatabase extends RoomDatabase {
public abstract DownloadDao getDownloadDao();
public abstract MovieDao getMovieDao();
}
完成上面的配置步骤Build一下Project,会自动生成AdsDataBase_Impl对象,它继承自RoomDatabase在内部包含了SupportSQLiteOpenHelper对象,该对象的实现类内部包含了SQLiteOpenHelper对象负责管理Sqlite数据库的交互任务,在编译时通过android.arch.persistence.room:compiler库中的APT Processor处理, 这些Processor会查看注解了@DataBase的数据库类,注解@Entity的实体类,注解@Dao的数据请求接口,根据根据实体类生成数据库表,根据Dao接口中的SQL语句生成数据库查询方法,这些都是编译时自动生成的保证了数据库存取的高效率,开发者只需要调用获取Dao接口就能够对数据库做CRUD操作。
Android中四大组件之一的Service组件主要负责在后台长时间运行不需要界面的任务,不过Service在后台运行需要消耗电量导致手机的续航能力差,谷歌Android引入了睡眠模式,在这种模式下网络、GPS等耗电功能都被禁止直到用户重新点亮屏幕。为此Android7.0引入了JobSchedule工具,所有的后台任务都提交给JobSchedule服务处理,它会在某些不确定的时间唤醒Android系统并执行提交给它的任务,不过在7.0上JobSchedule在重新启动后无法继续执行之前的任务,到了8.0系统才解决这个BUG,因而8.0之前版本的异步任务都需要提交给AlarmService来实现。WorkManager封装了这两种接口并且提供了工作队列,当多个任务被提交会执行不同的调度方法,确保所有任务的顺利执行。
/** 适用于即使进程退出依然运行在后台的工作,如果进程退出任务不必存在推荐
使用线程池。在>=23版本使用的是Job Schedule实现,低于23版本使用AlarmManager
实现,WorkManager封装了二者的差别提供统一的接口,用户不必担心版本适配问题,
只需要专注于自己的业务。
*/
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
scheduler = new SystemAlarmScheduler(context);
enableSystemAlarmService = true;
Logger.get().debug(TAG, "Created SystemAlarmScheduler");
}
implementation "android.arch.work:work-runtime:$rootProject.work_version“
public class DatabaseWorker extends Worker {
@Override
public Result doWork() {
for (int i = 0; i < 200; i++) {
MovieEntity entity = data.get(i % 3);
AdsDatabase.getInstance().getMovieDao().save(entity);
}
return Result.success();
}
}
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(DatabaseWorker.class).build();
WorkManager.getInstance().enqueue(request);
Android开发中几乎所有的展示组件都需要由Activity或Fragment承载,系统通过它们的生命周期函数来对UI资源做管理操作。比如Activity就有onCreate、onStart、onResume等多个生命周期函数,有些工作需要在Activity可见的情况下才需要执行。通常设计的组件都会暴漏出对应的生命周期接口调用,通过在Activity的生命周期函数里回调来感知外部Activity的运行状态。这种设计会导致组件多出那些不需要监控的接口,而且在Activity的生命周期添加额外的代码也会使得二者高度耦合。引入lifecycle组件使用观察者加状态机实现Activity的生命周期感知,所有需要感知外部生命周期的组件都面向这个接口实现,不必关心外部的承载具体是Activity或者Fragment。
implementation "android.arch.lifecycle:runtime:$rootProject.lifecycle_version"
annotationProcessor "android.arch.lifecycle:compiler:$rootProject.lifecycle_version"
// use kapt for Kotlin
// alternately - if using Java8, use the following instead of compiler
implementation "android.arch.lifecycle:common-java8:$rootProject.lifecycle_version"
// optional - ReactiveStreams support for LiveData
implementation "android.arch.lifecycle:reactivestreams:$rootProject.lifecycle_version“
接入LifecycleOwner接口
Support26.0.1之后的兼容包里的Activity、Fragment都已经集成了Lifecycle,之前的兼容包和Android包下Activity和Fragment的都需要手动实现LifecycleOwer接口。
注册LifecycleObserver
getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) {
Log.e(TAG, "onStart");
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
Log.e(TAG, "onStop");
}
});
这里使用普通的Activity中展示一个竖向轮播控件,并且提供一个Dialog样式的Activity,当轮播控件处于DialogActivity后方时就需要暂停轮播,当DialogActivity退出返回轮播控件需要重新开始播放,可以在Activity的onPause和onResume里做暂停和结束,不过现在使用Lifecyle就只需要将控件和Activity的生命周期绑定,在竖向轮播内部监听到当前Activity进入后台就暂停,回到前台继续竖向轮播。
// 竖向轮播控件代码
public class VerticalScrollView extends FrameLayout {
public void bindLifecycle(LifecycleOwner lifecycleOwner) {
lifecycleOwner.getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onResume(@NonNull LifecycleOwner owner) {
if (adapter != null) {
resumePlay();
}
}
@Override
public void onPause(@NonNull LifecycleOwner owner) {
if (adapter != null) {
pausePlay();
}
}
@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
destroy();
}
});
}
// 普通Activity代码,需要手动实现LifecycleOwner
public class CommonActivityTestActivity extends Activity implements LifecycleOwner {
private static final String TAG = "CommonActivityTestActiv";
private LifecycleRegistry lifecycleRegistry;
private VerticalScrollView verticalScrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
lifecycleRegistry = new LifecycleRegistry(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_test);
verticalScrollView = findViewById(R.id.verticalScrollView);
verticalScrollView.setAdapter(new VerticalAdapter(this));
// 将竖向轮播控件绑定到Activity生命周期
verticalScrollView.bindLifecycle(this);
}
// 在生命周期函数中向Lifecycle发送事件
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
// 在生命周期函数中向Lifecycle发送事件
@Override
protected void onStart() {
super.onStart();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
// 在生命周期函数中向Lifecycle发送事件
@Override
protected void onResume() {
super.onResume();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}
// 在生命周期函数中向Lifecycle发送事件
@Override
protected void onPause() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
super.onPause();
}
// 在生命周期函数中向Lifecycle发送事件
@Override
protected void onStop() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
super.onStop();
}
// 在生命周期函数中向Lifecycle发送事件
@Override
protected void onDestroy() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
super.onDestroy();
}
// 获取生命周期对象
@NonNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
public void onShowDialog(View view) {
Intent intent = new Intent(this, DialogActivity.class);
startActivity(intent);
}
}
Demo里的Activity需要加很多代码,实际开发中可以将这些代发都放到BaseActivity里,项目中所有的Activity都继承自BaseActivity这样就不用每个Activity都加入Lifecycle的逻辑,对于使用support26+的SupportActivity已经集成了LifecycleOwner功能,不必再添加实现。
在Lifecycle里定义了五种状态,分别是初始化、已创建、已开始、已展示以及已销毁,在Activity或Fragment中当相对的生命周期函数被调用就会向Lifecyle发送创建、开始、展示和销毁等动作,Lifecycle中的生命周期根据当前的动作产生状态迁移。对于绑定了Lifecycle的对象可以根据当前的生命周期状态决定自己要做那些响应。
了解了Lifecycle的状态迁移现在再看看具体的实现类,LifecycleOwner提供其他组件获取Activity/Fragment的生命周期Lifecyle对象,LifecycleRegistry对象则具体实现了Lifecycle内部的状态记录和状态迁移并且提供了观察者注册的接口,当生命周期发生变化的时候通知观察者,它也负责接收从Activity/Fragment回调函数发送来的事件。
在Android中通常会在Activity或者Fragment里保存View对应的数据,这些数据往往需要从网络或者磁盘请求得到,每当Activity发生配置变化或者进入后台被销毁就会重建它们,之前内存中保存的数据有需要从网络或磁盘重新拉取。Android内置的onSaveInstanceState/onRestoreInstanceState机制只能保存较小的数据或者能够支持序列化的数据类型,对于大量的数据依然很消耗性能。为此提供了局部的全局变量ViewModel组件,它能够跟Activity绑定,即使Activity因为配置变化或者被回收也依然保存在内存中,这样当Activity重建时就能够直接获取上次请求的数据快速展示出来。
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$rootProject.lifecycle_version"
// alternatively - just ViewModel
implementation "android.arch.lifecycle:viewmodel:$rootProject.lifecycle_version"
public class TestViewModel extends ViewModel {
public String name;
}
viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
这里我们定义一个普通的Activity并且让它的内部包含一个name字段,通过一个输入框和点击按钮设置name属性,同时把把输入的值存储到ViewModel中,之后通过旋转Activity方向会发现新的Activity里name值为空而ViewModel中的值依然存在。
public class ViewModelRotateActivity extends AppCompatActivity {
private static final String TAG = "ViewModelRotateActivity";
private String name;
private TestViewModel viewModel;
private EditText text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_model_rotate);
text = findViewById(R.id.text);
viewModel = ViewModelProviders.of(this).get(TestViewModel.class);
Log.e(TAG, viewModel.toString());
Toast.makeText(this, "name = " + name + ", viewModel.name = " + viewModel.name, Toast.LENGTH_LONG).show();
}
public void saveName(View view) {
// 分别将输入值保存再Activity的name里和ViewModel里
name = text.getText().toString();
viewModel.name = name;
if (!TextUtils.isEmpty(name) || !TextUtils.isEmpty(viewModel.name)) {
Toast.makeText(this, "name = " + name + ", viewModel.name = " + viewModel.name, Toast.LENGTH_LONG).show();
}
}
// 屏幕竖向展示
public void rotatePortrait(View view) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
// 屏幕横向展示
public void rotateLandscape(View view) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
老版本中的Activity框架会为它生成HolderFragment,该Fragment内部有ViewModelStores对象,Activity在被动销毁的情况下不会销毁Fragment对象,之后新建的Activity依然会复用老的ViewModelStores对象;对于已经实现了ViewModelStoreOwner的Activity使用onNonConfigSaveInstance()保存内部的ViewModelStore对象,之后在onCreate方法中会将之前保存的ViewModelStore对象重新赋值给新Activity,这样旧的Activity数据就转移到了新Activity名下。
通常后台从网络请求到数据会在主线程直接设置到UI元素上,但是当Activity并非当前与用户交互的Activity时在后台默默更新的UI会占用主线程的资源,如果更新量比较大就可能导致交互界面的卡顿。LiveData包装的数据会监听数据变化并且能够感知当前Activity/Fragment的生命周期,当它们处在非可见装填时并不会实时更新,当它们重新变为用户交互界面时才更新界面。这里的监听数据变化采用的是观察者模式,感知生命周期则通过前面的lifecycle组件实现。
implementation "android.arch.lifecycle:livedata:$rootProject.lifecycle_version“
private MutableLiveData name = new MutableLiveData<>();
name.observe(this, new Observer() {
@Override
public void onChanged(@Nullable String newName) {
text.setText("姓名:" + newName);
}
});
name.setValue(newValue);
Activity内部有一个MutableLiveData类型的name,当用户在当前Activity修改时会立即将数据更新到界面上,当用户打开DialogActivity并且使用更新数据时后台不会立即更新到界面上,当DialogActivity退出回到当前Activity此时数据会被立即更新到界面上。
public class LiveDataTestActivity extends AppCompatActivity {
private MutableLiveData name = new MutableLiveData<>();
private TextView text;
private int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_data_test);
text = findViewById(R.id.text);
text.setText("姓名: 张三" + count++);
name.observe(this, new Observer() {
@Override
public void onChanged(@Nullable String newName) {
text.setText("姓名:" + newName);
}
});
// 接收从DialogActivity发送过来的广播更新数据
LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
changeName();
}
}, new IntentFilter("com.sohu.change_value"));
}
// 当前界面更新数据
public void onChangeName(View view) {
changeName();
}
private void changeName() {
String newValue = "张三" + (count++);
Toast.makeText(getApplicationContext(), "设置新值:" + newValue, Toast.LENGTH_SHORT).show();
name.setValue(newValue);
}
public void onShowDialog(View view) {
Intent intent = new Intent(this, ChangeDialogActivity.class);
startActivity(intent);
}
}
LiveData内部会保存当前数据,同时也会保存生命周期,当数据发生改变时如果在活动的状态就出发Observer的操作,否则本次操作不会被触发需要等到下次进入活动状态再触发更新。
以上使用的所有测试Demo下载地址,欢迎访问!