Android JetPack架构组件介绍(一)

Android在2018年的Google大会推出了Android JetPack工具库,这个工具库将常用的功能统一封装起来,提高开发者的开发效率,提高代码的复用性。目前JetPack主要封装的功能包含基础框架、应用架构、应用行为和UI界面四个方面,除了应用架构比较复杂外其他的使用起来都相对简单,这里主要介绍JetPack的架构组件功能。

Room数据库框架

Android的数据库是基于SQLite数据库的简单封装,用户需要继承SQLiteOpenHelper类实现数据的增删改查,手动转换关系型数据到Java对象数据的操作繁琐而且很容易产生大量的重复代码。目前市面上有不少第三方开源的ORM库,不过这些库的体积庞大,有些还使用反射容易造成性能问题。
Room ORM框架基于注解和APT在编译时生成代码,用户只需要简单配置实体对象就能够正确生成数据库表,所有数据库操作都只需要用户提供对应的SQL语句,查询工作完全由框架生成模板代码。ROOM框架封装后的数据库逻辑完全是面向对象的实现方式,能够轻松的集成到Android开发项目中。

使用步骤

  1. 框架引入
   implementation  "android.arch.persistence.room:runtime:$rootProject.roomVersion"
   annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"
   androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion“
  1. 构建数据库对象
Room.databaseBuilder(sContext, AdsDatabase.class, "ads_database.db")
         .addCallback(new Callback)
         .addMigrations(new Migration(1, 2) )
         .allowMainThreadQueries()
         .build();
  1. 配置实体
@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;
}
  1. 定义Dao对象
@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<DownloadEntity> queryByStatus(int[] status);
}
  1. DB类增加注解实体,添加Dao返回接口
@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操作。
在这里插入图片描述

WorkManager组件

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");
}

使用步骤

  1. 引用组件
 implementation "android.arch.work:work-runtime:$rootProject.work_version“
  1. 定义任务
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();
    }
}
  1. 提交任务
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(DatabaseWorker.class).build();
WorkManager.getInstance().enqueue(request);

Lifecycle组件

Android开发中几乎所有的展示组件都需要由Activity或Fragment承载,系统通过它们的生命周期函数来对UI资源做管理操作。比如Activity就有onCreate、onStart、onResume等多个生命周期函数,有些工作需要在Activity可见的情况下才需要执行。通常设计的组件都会暴漏出对应的生命周期接口调用,通过在Activity的生命周期函数里回调来感知外部Activity的运行状态。这种设计会导致组件多出那些不需要监控的接口,而且在Activity的生命周期添加额外的代码也会使得二者高度耦合。引入lifecycle组件使用观察者加状态机实现Activity的生命周期感知,所有需要感知外部生命周期的组件都面向这个接口实现,不必关心外部的承载具体是Activity或者Fragment。

使用步骤

  1. 组件引入
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“ 
  1. 接入LifecycleOwner接口
    Support26.0.1之后的兼容包里的Activity、Fragment都已经集成了Lifecycle,之前的兼容包和Android包下Activity和Fragment的都需要手动实现LifecycleOwer接口。

  2. 注册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");
            }
});

Demo演示

这里使用普通的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回调函数发送来的事件。
在这里插入图片描述

ViewModel组件

在Android中通常会在Activity或者Fragment里保存View对应的数据,这些数据往往需要从网络或者磁盘请求得到,每当Activity发生配置变化或者进入后台被销毁就会重建它们,之前内存中保存的数据有需要从网络或磁盘重新拉取。Android内置的onSaveInstanceState/onRestoreInstanceState机制只能保存较小的数据或者能够支持序列化的数据类型,对于大量的数据依然很消耗性能。为此提供了局部的全局变量ViewModel组件,它能够跟Activity绑定,即使Activity因为配置变化或者被回收也依然保存在内存中,这样当Activity重建时就能够直接获取上次请求的数据快速展示出来。

使用步骤

  1. 组件引入
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$rootProject.lifecycle_version"
// alternatively - just ViewModel
implementation "android.arch.lifecycle:viewmodel:$rootProject.lifecycle_version"
  1. 创建ViewModel类
public class TestViewModel extends ViewModel {
      public String name;
}
  1. 使用ViewModel对象
viewModel = ViewModelProviders.of(this).get(TestViewModel.class);

Demo演示

这里我们定义一个普通的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名下。
在这里插入图片描述

LiveData组件

通常后台从网络请求到数据会在主线程直接设置到UI元素上,但是当Activity并非当前与用户交互的Activity时在后台默默更新的UI会占用主线程的资源,如果更新量比较大就可能导致交互界面的卡顿。LiveData包装的数据会监听数据变化并且能够感知当前Activity/Fragment的生命周期,当它们处在非可见装填时并不会实时更新,当它们重新变为用户交互界面时才更新界面。这里的监听数据变化采用的是观察者模式,感知生命周期则通过前面的lifecycle组件实现。

使用步骤

  1. 引入组件
 implementation "android.arch.lifecycle:livedata:$rootProject.lifecycle_version“
  1. 定义变量
private MutableLiveData<String> name = new MutableLiveData<>();
name.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String newName) {
                text.setText("姓名:" + newName);
            }
        });
  1. 更新变量
name.setValue(newValue);

Demo演示

Activity内部有一个MutableLiveData类型的name,当用户在当前Activity修改时会立即将数据更新到界面上,当用户打开DialogActivity并且使用更新数据时后台不会立即更新到界面上,当DialogActivity退出回到当前Activity此时数据会被立即更新到界面上。

public class LiveDataTestActivity extends AppCompatActivity {
    private MutableLiveData<String> 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<String>() {
            @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下载地址,欢迎访问!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值