哥哥帶你學(xué)Jatpack

聲明,這里是我平時(shí)日常的筆記Zone油啤,所以記錄可能會(huì)偏向于我認(rèn)為的重點(diǎn)區(qū)域,會(huì)有些疏漏或者缺失的地方蟀苛,或是排版或者文案有些凌亂益咬,但我既然發(fā)布出來(lái)了,我會(huì)盡全力去完善帜平,各位看官大家一起努力幽告,愿意聽到各位的批評(píng)指正,共同進(jìn)步……有問(wèn)題可聯(lián)系微信dk2582525775裆甩,期待騷擾……

1冗锁,Room數(shù)據(jù)庫(kù)測(cè)試

Room ORM框架基于注解和APT在編譯時(shí)生成代碼,用戶只需要簡(jiǎn)單配置實(shí)體對(duì)象就能夠正確生成數(shù)據(jù)庫(kù)表嗤栓,所有數(shù)據(jù)庫(kù)操作都只需要用戶提供對(duì)應(yīng)的SQL語(yǔ)句冻河,查詢工作完全由框架生成模板代碼。ROOM框架封裝后的數(shù)據(jù)庫(kù)邏輯完全是面向?qū)ο蟮膶?shí)現(xiàn)方式茉帅,能夠輕松的集成到Android開發(fā)項(xiàng)目中叨叙。

使用步驟

框架引入

? implementation? "android.arch.persistence.room:runtime:$rootProject.roomVersion"

? annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"

? androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion“

構(gòu)建數(shù)據(jù)庫(kù)對(duì)象

Room.databaseBuilder(sContext, AdsDatabase.class, "ads_database.db")

? ? ? ? .addCallback(new Callback)

? ? ? ? .addMigrations(new Migration(1, 2) )

? ? ? ? .allowMainThreadQueries()

? ? ? ? .build();


配置實(shí)體

@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對(duì)象

@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);

}

DB類增加注解實(shí)體,添加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堪澎,會(huì)自動(dòng)生成AdsDataBase_Impl對(duì)象擂错,它繼承自RoomDatabase在內(nèi)部包含了SupportSQLiteOpenHelper對(duì)象,該對(duì)象的實(shí)現(xiàn)類內(nèi)部包含了SQLiteOpenHelper對(duì)象負(fù)責(zé)管理Sqlite數(shù)據(jù)庫(kù)的交互任務(wù)樱蛤,在編譯時(shí)通過(guò)android.arch.persistence.room:compiler庫(kù)中的APT Processor處理马昙, 這些Processor會(huì)查看注解了@DataBase的數(shù)據(jù)庫(kù)類桃犬,注解@Entity的實(shí)體類,注解@Dao的數(shù)據(jù)請(qǐng)求接口行楞,根據(jù)根據(jù)實(shí)體類生成數(shù)據(jù)庫(kù)表,根據(jù)Dao接口中的SQL語(yǔ)句生成數(shù)據(jù)庫(kù)查詢方法土匀,這些都是編譯時(shí)自動(dòng)生成的保證了數(shù)據(jù)庫(kù)存取的高效率子房,開發(fā)者只需要調(diào)用獲取Dao接口就能夠?qū)?shù)據(jù)庫(kù)做CRUD操作。

public void onInsert(View view) {

DownloadDao dao = AdsDatabase.getInstance().getDownloadDao();

? ? DownloadEntity downloadEntity =new DownloadEntity("http://www.baidu.com",

? ? ? ? ? ? 1000000, 2900000, 3, 1, "test");

? ? Log.e(TAG, "Insert " + downloadEntity.toString());

? ? Logger.d(TAG, "Insert " + downloadEntity.toString());

? ? dao.insert(downloadEntity);

}

public void onQuery(View view) {

DownloadDao dao = AdsDatabase.getInstance().getDownloadDao();

? ? List list = dao.queryByStatus(new int[] {3 });

? ? Log.e(TAG, list.toString());

? ? Logger.d(TAG, list.toString());

}

public void onDelete(View view) {

DownloadDao dao = AdsDatabase.getInstance().getDownloadDao();

? ? dao.deleteAll();

? ? Log.e(TAG, "Delete all download entities!");

? ? Logger.d(TAG, "Delete all download entities!");

}

2就轧,生命周期測(cè)試Lifecycle框架測(cè)試

通常設(shè)計(jì)的組件都會(huì)暴漏出對(duì)應(yīng)的生命周期接口調(diào)用证杭,通過(guò)在Activity的生命周期函數(shù)里回調(diào)來(lái)感知外部Activity的運(yùn)行狀態(tài)。這種設(shè)計(jì)會(huì)導(dǎo)致組件多出那些不需要監(jiān)控的接口妒御,而且在Activity的生命周期添加額外的代碼也會(huì)使得二者高度耦合解愤。引入lifecycle組件使用觀察者加狀態(tài)機(jī)實(shí)現(xiàn)Activity的生命周期感知,所有需要感知外部生命周期的組件都面向這個(gè)接口實(shí)現(xiàn)乎莉,不必關(guān)心外部的承載具體是Activity或者Fragment送讲。注意要在api24以后。

使用步驟

組件引入

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都已經(jīng)集成了Lifecycle哼鬓,之前的兼容包和Android包下Activity和Fragment的都需要手動(dòng)實(shí)現(xiàn)LifecycleOwer接口。

注冊(cè)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中展示一個(gè)豎向輪播控件边灭,并且提供一個(gè)Dialog樣式的Activity异希,當(dāng)輪播控件處于DialogActivity后方時(shí)就需要暫停輪播,當(dāng)DialogActivity退出返回輪播控件需要重新開始播放绒瘦,可以在Activity的onPause和onResume里做暫停和結(jié)束称簿,不過(guò)現(xiàn)在使用Lifecyle就只需要將控件和Activity的生命周期綁定,在豎向輪播內(nèi)部監(jiān)聽到當(dāng)前Activity進(jìn)入后臺(tái)就暫停惰帽,回到前臺(tái)繼續(xù)豎向輪播憨降。

豎向輪播控件代碼

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代碼,需要手動(dòng)實(shí)現(xiàn)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);

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? protected void onSaveInstanceState(Bundle outState) {

? ? ? ? super.onSaveInstanceState(outState);

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? @Override

? ? protected void onStart() {

? ? ? ? super.onStart();

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? @Override

? ? protected void onResume() {

? ? ? ? super.onResume();

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? @Override

? ? protected void onPause() {

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);

? ? ? ? super.onPause();

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? @Override

? ? protected void onStop() {

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);

? ? ? ? super.onStop();

? ? }

// 在生命周期函數(shù)中向Lifecycle發(fā)送事件

? ? @Override

? ? protected void onDestroy() {

? ? ? ? lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);

? ? ? ? super.onDestroy();

? ? }

// 獲取生命周期對(duì)象

? ? @NonNull

? ? @Override

? ? public Lifecycle getLifecycle() {

? ? ? ? return lifecycleRegistry;

? ? }

? ? public void onShowDialog(View view) {

? ? ? ? Intent intent = new Intent(this, DialogActivity.class);

? ? ? ? startActivity(intent);

? ? }

}

Demo里的Activity需要加很多代碼善茎,實(shí)際開發(fā)中可以將這些代發(fā)都放到BaseActivity里券册,項(xiàng)目中所有的Activity都繼承自BaseActivity這樣就不用每個(gè)Activity都加入Lifecycle的邏輯,對(duì)于使用support26+的SupportActivity已經(jīng)集成了LifecycleOwner功能垂涯,不必再添加實(shí)現(xiàn)烁焙。

3,WorkManager組件

Android中四大組件之一的Service組件主要負(fù)責(zé)在后臺(tái)長(zhǎng)時(shí)間運(yùn)行不需要界面的任務(wù)耕赘,不過(guò)Service在后臺(tái)運(yùn)行需要消耗電量導(dǎo)致手機(jī)的續(xù)航能力差骄蝇,谷歌Android引入了睡眠模式,在這種模式下網(wǎng)絡(luò)操骡、GPS等耗電功能都被禁止直到用戶重新點(diǎn)亮屏幕九火。為此Android7.0引入了JobSchedule工具赚窃,所有的后臺(tái)任務(wù)都提交給JobSchedule服務(wù)處理,它會(huì)在某些不確定的時(shí)間喚醒Android系統(tǒng)并執(zhí)行提交給它的任務(wù)岔激,不過(guò)在7.0上JobSchedule在重新啟動(dòng)后無(wú)法繼續(xù)執(zhí)行之前的任務(wù)勒极,到了8.0系統(tǒng)才解決這個(gè)BUG,因而8.0之前版本的異步任務(wù)都需要提交給AlarmService來(lái)實(shí)現(xiàn)虑鼎。WorkManager封裝了這兩種接口并且提供了工作隊(duì)列辱匿,當(dāng)多個(gè)任務(wù)被提交會(huì)執(zhí)行不同的調(diào)度方法,確保所有任務(wù)的順利執(zhí)行炫彩。

/** 適用于即使進(jìn)程退出依然運(yùn)行在后臺(tái)的工作匾七,如果進(jìn)程退出任務(wù)不必存在推薦

使用線程池。在>=23版本使用的是Job Schedule實(shí)現(xiàn)江兢,低于23版本使用AlarmManager

實(shí)現(xiàn)昨忆,WorkManager封裝了二者的差別提供統(tǒng)一的接口,用戶不必?fù)?dān)心版本適配問(wèn)題杉允,

只需要專注于自己的業(yè)務(wù)邑贴。

*/

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“

定義任務(wù)

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();

? ? }

}

提交任務(wù)

OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(DatabaseWorker.class).build();

WorkManager.getInstance().enqueue(request);

4,viewmodel組件

在Android中通常會(huì)在Activity或者Fragment里保存View對(duì)應(yīng)的數(shù)據(jù)夺颤,這些數(shù)據(jù)往往需要從網(wǎng)絡(luò)或者磁盤請(qǐng)求得到痢缎,每當(dāng)Activity發(fā)生配置變化或者進(jìn)入后臺(tái)被銷毀就會(huì)重建它們,之前內(nèi)存中保存的數(shù)據(jù)有需要從網(wǎng)絡(luò)或磁盤重新拉取世澜。Android內(nèi)置的onSaveInstanceState/onRestoreInstanceState機(jī)制只能保存較小的數(shù)據(jù)或者能夠支持序列化的數(shù)據(jù)類型独旷,對(duì)于大量的數(shù)據(jù)依然很消耗性能。為此提供了局部的全局變量ViewModel組件寥裂,它能夠跟Activity綁定嵌洼,即使Activity因?yàn)榕渲米兓蛘弑换厥找惨廊槐4嬖趦?nèi)存中,這樣當(dāng)Activity重建時(shí)就能夠直接獲取上次請(qǐng)求的數(shù)據(jù)快速展示出來(lái)封恰。

使用步驟

組件引入

// ViewModel and LiveData

implementation "android.arch.lifecycle:extensions:$rootProject.lifecycle_version"

// alternatively - just ViewModel

implementation "android.arch.lifecycle:viewmodel:$rootProject.lifecycle_version"

創(chuàng)建ViewModel類

public class TestViewModel extends ViewModel {

? ? ? public String name;

}

使用ViewModel對(duì)象

viewModel = ViewModelProviders.of(this).get(TestViewModel.class);

Demo演示

這里我們定義一個(gè)普通的Activity并且讓它的內(nèi)部包含一個(gè)name字段麻养,通過(guò)一個(gè)輸入框和點(diǎn)擊按鈕設(shè)置name屬性,同時(shí)把把輸入的值存儲(chǔ)到ViewModel中诺舔,之后通過(guò)旋轉(zhuǎn)Activity方向會(huì)發(fā)現(xiàn)新的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);

? ? }

}

5,liveData 組件

通常后臺(tái)從網(wǎng)絡(luò)請(qǐng)求到數(shù)據(jù)會(huì)在主線程直接設(shè)置到UI元素上低飒,但是當(dāng)Activity并非當(dāng)前與用戶交互的Activity時(shí)在后臺(tái)默默更新的UI會(huì)占用主線程的資源许昨,如果更新量比較大就可能導(dǎo)致交互界面的卡頓。LiveData包裝的數(shù)據(jù)會(huì)監(jiān)聽數(shù)據(jù)變化并且能夠感知當(dāng)前Activity/Fragment的生命周期褥赊,當(dāng)它們處在非可見裝填時(shí)并不會(huì)實(shí)時(shí)更新糕档,當(dāng)它們重新變?yōu)橛脩艚换ソ缑鏁r(shí)才更新界面。這里的監(jiān)聽數(shù)據(jù)變化采用的是觀察者模式拌喉,感知生命周期則通過(guò)前面的lifecycle組件實(shí)現(xiàn)速那。

使用步驟

引入組件

implementation "android.arch.lifecycle:livedata:$rootProject.lifecycle_version“

定義變量

private MutableLiveData<String> name = new MutableLiveData<>();

name.observe(this, new Observer<String>() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onChanged(@Nullable String newName) {

? ? ? ? ? ? ? ? text.setText("姓名:" + newName);

? ? ? ? ? ? }

? ? ? ? });

更新變量

name.setValue(newValue);

Demo演示

Activity內(nèi)部有一個(gè)MutableLiveData類型的name俐银,當(dāng)用戶在當(dāng)前Activity修改時(shí)會(huì)立即將數(shù)據(jù)更新到界面上,當(dāng)用戶打開DialogActivity并且使用更新數(shù)據(jù)時(shí)后臺(tái)不會(huì)立即更新到界面上,當(dāng)DialogActivity退出回到當(dāng)前Activity此時(shí)數(shù)據(jù)會(huì)被立即更新到界面上。

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發(fā)送過(guò)來(lái)的廣播更新數(shù)據(jù)

? ? ? ? LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onReceive(Context context, Intent intent) {

? ? ? ? ? ? ? ? changeName();

? ? ? ? ? ? }

? ? ? ? }, new IntentFilter("com.sohu.change_value"));

? ? }

? // 當(dāng)前界面更新數(shù)據(jù)

? ? public void onChangeName(View view) {

? ? ? ? changeName();

? ? }

? ? private void changeName() {

? ? ? ? String newValue = "張三" + (count++);

? ? ? ? Toast.makeText(getApplicationContext(), "設(shè)置新值:" + newValue, Toast.LENGTH_SHORT).show();

? ? ? ? name.setValue(newValue);

? ? }

? ? public void onShowDialog(View view) {

? ? ? ? Intent intent = new Intent(this, ChangeDialogActivity.class);

? ? ? ? startActivity(intent);

? ? }

}

6,paging組件

Android應(yīng)用中列表是一種很常見的展現(xiàn)形式,多條數(shù)據(jù)展示就會(huì)使用多個(gè)界面元素翎卓,如果大量的數(shù)據(jù)一次性全部加入應(yīng)用中會(huì)導(dǎo)致內(nèi)存極度消耗,而且用戶也很難一次性完全瀏覽一遍,列表數(shù)據(jù)通常都是通過(guò)分頁(yè)加載的形式展現(xiàn)道媚。JetPack內(nèi)部包含了支持RecyclerView分頁(yè)功能的Paging組件,只需要設(shè)置數(shù)據(jù)獲取來(lái)源和每頁(yè)數(shù)量回窘,組件在用戶瀏覽列表是會(huì)自動(dòng)計(jì)算下一頁(yè)要請(qǐng)求的數(shù)據(jù)并發(fā)送請(qǐng)求诺擅。

使用步驟

導(dǎo)入框架

implementation "android.arch.paging:runtime:$rootProject.paging_version“

編寫數(shù)據(jù)庫(kù)

@Query("select * from tb_movie")

DataSource.Factory<Integer, MovieEntity> getAllMovies();

編寫列表界面

recyclerView.setLayoutManager(new LinearLayoutManager(this));

recyclerView.setAdapter(adapter = new PagedMovieAdapter());

生成LivedPagedList對(duì)象

pagedList = new LivePagedListBuilder<>(repository.getAllMovies(), new PagedList.Config.Builder()

? ? ? ? ? ? ? ? .setPageSize(10).setPrefetchDistance(2).setInitialLoadSizeHint(20).build()).build();

pagedList.observe(this, new Observer<PagedList<MovieEntity>>() {

public void onChanged(@Nullable PagedList<MovieEntity> movieEntities) {

? ? ? ? ? ? ? ? adapter.submitList(movieEntities);

? ? ? }});

Demo演示

前面通過(guò)Room生成MovieEntity對(duì)象的數(shù)據(jù)庫(kù)表并且定義獲取所有電影數(shù)據(jù)的接口,需要注意返回的對(duì)象類型是DataSource.Factory類型啡直,之后就按照使用步驟中的實(shí)現(xiàn)方式將PagedList提交到PagedAdapter中烁涌。

7,Navigation組件

幾乎所有的應(yīng)用內(nèi)部都會(huì)包含導(dǎo)航功能酒觅,通過(guò)導(dǎo)航能夠自由的切換用戶界面撮执。Android中的Fragment和Activity都可以用來(lái)做界面切換,直接使用代碼做切換需要針對(duì)Activity和Fragment使用不同邏輯舷丹,而且導(dǎo)航功能被分散到多個(gè)地方很難直觀了解界面之間的前后關(guān)系抒钱。Navigation組件通過(guò)資源文件定義所有要導(dǎo)航到的界面,所有可以被導(dǎo)航到的界面都可以注冊(cè)颜凯,用戶通過(guò)統(tǒng)一的接口訪問(wèn)注冊(cè)信息并實(shí)現(xiàn)界面跳轉(zhuǎn)谋币。

目前的Android開發(fā)多采用多Activity+Fragment實(shí)現(xiàn),這種實(shí)現(xiàn)方式會(huì)暫用較多的資源症概,而且Activity的創(chuàng)建和銷毀都需要認(rèn)真處理蕾额,否則容易引起內(nèi)存泄漏。針對(duì)這個(gè)問(wèn)題就有開發(fā)者提出使用單Activity+Fragment的模式開發(fā)Android應(yīng)用彼城,Navigation組件對(duì)這種單Activity模式的開發(fā)提供了強(qiáng)力的支持诅蝶。

使用步驟

導(dǎo)入框架

? ? implementation "android.arch.navigation:navigation-fragment:$rootProject.nav_version"

? ? implementation "android.arch.navigation:navigation-ui:$rootProject.nav_version“

編寫界面

編寫Fragment界面

定義導(dǎo)航圖資源

要注意Android Studio需要3.2+版本,低版本的不支持導(dǎo)航編輯功能募壕。如果是3.2版本還需要手動(dòng)啟用Navigation編輯器调炬。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"

? ? xmlns:app="http://schemas.android.com/apk/res-auto"

? ? xmlns:tools="http://schemas.android.com/tools"

? ? android:id="@+id/nav_graph_test"

? ? app:startDestination="@id/fragmentThree">

<fragment

? ? ? ? android:id="@+id/fragmentThree"

? ? ? ? android:name="com.sohu.jetpacktest.nav.FragmentThree“>

<action android:id="@+id/action_two“ app:destination="@id/fragmentTwo" />

? ? </fragment>

? ? </navigation>

導(dǎo)航跳轉(zhuǎn)

Navigation.findNavController(view).navigate(R.id.action_two);

Demo演示

Demo中主要的是需要在承載的Activity中設(shè)置NavHostFragment,它會(huì)負(fù)責(zé)將導(dǎo)航資源文件里定義的各種目標(biāo)和動(dòng)作解析出來(lái)司抱。

public class NavigationTestActivity extends AppCompatActivity {

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_navigation_test);

? ? }

}

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

? ? xmlns:app="http://schemas.android.com/apk/res-auto"

? ? xmlns:tools="http://schemas.android.com/tools"

? ? android:layout_width="match_parent"

? ? android:layout_height="match_parent"

? ? tools:context=".NavigationTestActivity">

? ? <fragment

? ? ? ? android:id="@+id/my_nav_host_fragment"

? ? ? ? android:name="androidx.navigation.fragment.NavHostFragment"

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="match_parent"

? ? ? ? app:defaultNavHost="true"

? ? ? ? app:navGraph="@navigation/nav_graph_test" />

</FrameLayout>

可以看到導(dǎo)航圖定義在R.navigation.nav_graph_test資源文件中筐眷,該資源文件的定義也很簡(jiǎn)單。app:startDestination代表進(jìn)入導(dǎo)航圖時(shí)默認(rèn)展示的界面也就是fragmentOne习柠,navigation標(biāo)簽里定義了fragment類型和activity類型的目標(biāo)匀谣,在目標(biāo)內(nèi)部定義的動(dòng)作action可以設(shè)置跳轉(zhuǎn)的目標(biāo)照棋,跳轉(zhuǎn)時(shí)需要的參數(shù)等。

<?xml version="1.0" encoding="utf-8"?>

<navigation xmlns:android="http://schemas.android.com/apk/res/android"

? ? xmlns:app="http://schemas.android.com/apk/res-auto"

? ? xmlns:tools="http://schemas.android.com/tools"

? ? android:id="@+id/nav_graph_test"

? ? app:startDestination="@id/fragmentOne">

? ? <action android:id="@+id/action_global_one"

? ? ? ? app:destination="@id/fragmentOne" />

? ? <fragment

? ? ? ? android:id="@+id/fragmentOne"

? ? ? ? android:name="com.sohu.jetpacktest.nav.FragmentOne"

? ? ? ? android:label="fragment_fragment_one"

? ? ? ? tools:layout="@layout/fragment_fragment_one" >

? ? ? ? <action android:id="@+id/action_two"

? ? ? ? ? ? app:destination="@id/fragmentTwo" />

? ? ? ? <action android:id="@+id/action_room"

? ? ? ? ? ? app:destination="@id/roomActivity" />

? ? </fragment>

? ? <fragment

? ? ? ? android:id="@+id/fragmentTwo"

? ? ? ? android:name="com.sohu.jetpacktest.nav.FragmentTwo"

? ? ? ? android:label="fragment_fragment_two"

? ? ? ? tools:layout="@layout/fragment_fragment_two">

? ? </fragment>

? ? // ... fragmentThree fragmentFour

? ? <activity

? ? ? ? android:id="@+id/roomActivity"

? ? ? ? android:name="com.sohu.jetpacktest.RoomActivity"

? ? ? ? android:label="activity_room"

? ? ? ? tools:layout="@layout/activity_room" >

? ? ? ? <argument

? ? ? ? ? ? android:name="name"

? ? ? ? ? ? android:defaultValue="lisi"

? ? ? ? ? ? app:type="string" />

? ? </activity>

</navigation>

// fragmentOne

view.findViewById(R.id.gotoTwo).setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View view) {

? ? ? ? ? ? ? ? Navigation.findNavController(view).navigate(R.id.action_two);

? ? ? ? ? ? }

? ? ? ? });

view.findViewById(R.id.gotoRoom).setOnClickListener(new View.OnClickListener() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void onClick(View view) {

? ? ? ? ? ? ? ? Navigation.findNavController(view).navigate(R.id.action_room);

? ? ? ? ? ? }

? ? ? ? });

8武翎,DataBinding組件

使用步驟

導(dǎo)入框架

在需要應(yīng)用DataBinding的Module的gradle文件中添加:

dataBinding{

? ? ? ? enabled = true

? ? }

DataBinding的使用:

在原本的layout布局文件中將最外層的布局標(biāo)簽替換為:< layout >

<layout xmlns:android="http://schemas.android.com/apk/res/android"

? ? ? ? xmlns:tools="http://schemas.android.com/tools">

? ? <data>

? ? ? ? <variable

? ? ? ? ? ? name="item"

? ? ? ? ? ? type="com.wei.rxjavademo.Item"/>

? ? ? ? <variable

? ? ? ? ? ? name="presenter"

? ? ? ? ? ? type="com.wei.rxjavademo.MainActivity.Presenter"/>

? ? </data>

? ? <LinearLayout

? ? ? ? android:layout_width="match_parent"

? ? ? ? android:layout_height="match_parent"

? ? ? ? android:orientation="vertical"

? ? ? ? android:paddingBottom="@dimen/activity_vertical_margin"

? ? ? ? android:paddingLeft="@dimen/activity_horizontal_margin"

? ? ? ? android:paddingRight="@dimen/activity_horizontal_margin"

? ? ? ? android:paddingTop="@dimen/activity_vertical_margin"

? ? ? ? tools:context="com.wei.rxjavademo.MainActivity">

? ? ? ? <TextView

? ? ? ? ? ? android:layout_width="wrap_content"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="@{item.content}"/>

? ? ? ? <Button

? ? ? ? ? ? android:layout_width="match_parent"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="訂閱"/>

? ? ? ? <Button

? ? ? ? ? ? android:onClick="@{() -> presenter.onClickListenerBinding(item)}"

? ? ? ? ? ? android:layout_width="match_parent"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="取消訂閱"/>

? ? ? ? <EditText

? ? ? ? ? ? android:onTextChanged="@{presenter.onTextChanged}"

? ? ? ? ? ? android:layout_width="match_parent"

? ? ? ? ? ? android:layout_height="50dp"/>

? ? </LinearLayout>

</layout>

同時(shí)在布局文件中加入data標(biāo)簽烈炭,用于指定要綁定的數(shù)據(jù)類:

<data>

<variable

name=""

type=""/>

</data>

//例如

<data>

? ? ? ? <variable

? ? ? ? ? ? name="item"

? ? ? ? ? ? type="com.wei.rxjavademo.Item"/>

? ? ? ? <!--name可以隨意根據(jù)需要命名,type為該類型的全類名-->

? ? ? ? <variable

? ? ? ? ? ? name="presenter"

? ? ? ? ? ? type="com.wei.rxjavademo.MainActivity.Presenter"/>

</data>

修改完布局文件之后就可以在Java代碼中使用DataBinding了宝恶。

mItem = new Item(12, "hello");

? ? ? ? //AS會(huì)為每一個(gè)layout標(biāo)簽自動(dòng)生成一個(gè)Binding類符隙,用于綁定視圖

? ? ? ? activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);


? ? ? ? //向布局文件中的variable設(shè)置變量

? ? ? ? activityMainBinding.setItem(mItem);

? ? ? ? activityMainBinding.setPresenter(new Presenter());

Item只是自定義的一個(gè)JavaBean,里邊只有Id和Content兩個(gè)屬性及其get和set方法

以上就是DataBinding的數(shù)據(jù)的綁定垫毙,接下來(lái)還有方法的綁定霹疫。

方法的綁定有兩種情況:

方法引用綁定:主要是綁定一些已經(jīng)原有的方法事件,例如onClick综芥,onTextChanged這些方法

監(jiān)聽器綁定:主要是綁定一些自定義的方法事件丽蝎,可以支持傳入數(shù)據(jù)類型。

方法引用綁定:

自定義一個(gè)Presenter類膀藐,并在其中實(shí)現(xiàn)onClick屠阻,onTextChanged等需要的方法,然后在布局文件中直接聲明额各。

在Java中實(shí)現(xiàn)onClick方法

public class Presenter {

public void onClick(View view){

? ? ? ? ? ? Toast.makeText(MainActivity.this, "onClick點(diǎn)擊", Toast.LENGTH_SHORT).show();

? ? ? ? }

}

在布局文件中直接聲明方法的引用:

<Button

? android:onClick="@{presenter.onClick}"

? android:layout_width="match_parent"

? android:layout_height="wrap_content"

? android:text="訂閱"/>

<!--在onClick方法中直接聲明引用的方法為Presenter中的onClick方法-->

監(jiān)聽器綁定:

也是要先在Presenter中定義需要的方法国觉,并定義好參數(shù):

public class Presenter {

//在這個(gè)方法中可以添加需要的參數(shù)

public void onClickListenerBinding(Item item){

? ? ? ? ? ? Toast.makeText(MainActivity.this, item.getContent(), Toast.LENGTH_SHORT).show();

? ? ? ? }

}

只是在布局文件中引用的時(shí)候有所不同:

<Button

? ? ? ? ? ? android:onClick="@{() -> presenter.onClickListenerBinding(item)}"

? ? ? ? ? ? android:layout_width="match_parent"

? ? ? ? ? ? android:layout_height="wrap_content"

? ? ? ? ? ? android:text="取消訂閱"/>

<!--在布局文件中的使用方式為L(zhǎng)ambda表達(dá)式-->

9,WorkManager組件

1. 易于調(diào)度

WorkManager API可以輕松創(chuàng)建可延遲的異步任務(wù),并允許您指定應(yīng)該何時(shí)執(zhí)行虾啦。

你可以創(chuàng)建任務(wù)并將該任務(wù)交給WorkManager麻诀,以便立即或在設(shè)備處于特定條件下運(yùn)行該任務(wù)。

WorkManager提供了保證缸逃,即使您的應(yīng)用程序強(qiáng)制退出或設(shè)備重新啟動(dòng)针饥,你的任務(wù)仍會(huì)在特定條件匹配時(shí)執(zhí)行。

2. 易于取消

WorkManager給每個(gè)任務(wù)分配了UUID需频,使用這個(gè)唯一的ID你就可以隨時(shí)取消任務(wù)丁眼。

3.易于查詢

你可以使用分配給每個(gè)任務(wù)的唯一標(biāo)識(shí)來(lái)詢問(wèn)任務(wù)的狀態(tài),無(wú)論是正在運(yùn)行昭殉,掛起還是已完成苞七。

WorkManager API超越了任務(wù)的當(dāng)前狀態(tài),允許任務(wù)一鍵值對(duì)格式返回?cái)?shù)據(jù)挪丢。

WorkManager使用LiveData來(lái)干會(huì)任務(wù)的數(shù)據(jù)和狀態(tài)蹂风,所以,你的Activity可以觀察這個(gè)LiveData乾蓬,并且每當(dāng)任務(wù)完成時(shí)都會(huì)得到通知惠啄。

4.支持Android所有版本

WorkManager支持Android API 14及以上

WorkManager根據(jù)設(shè)備API級(jí)別和應(yīng)用程序狀態(tài)等因素選擇適當(dāng)?shù)姆绞絹?lái)運(yùn)行你的任務(wù)。

如果應(yīng)用程序正在運(yùn)行,WorkManager將創(chuàng)建新的線程來(lái)運(yùn)行任務(wù)撵渡。

如果應(yīng)用程序沒(méi)有運(yùn)行融柬,那么他將使用JobScheduler API或Firebase Job APIs調(diào)度者或Alarm manager API運(yùn)行調(diào)度任務(wù)。

添加依賴:

implementation "android.arch.work:work-runtime:$work_version"

implementation "android.arch.work:work-firebase:$work_version"

相關(guān)類和概念

Work manager APIs建立在幾個(gè)類上趋距,你必須繼承一些抽象類來(lái)安排任務(wù)粒氧。

Worker:在WorkManager世界中,Worker等同于需要在后臺(tái)執(zhí)行的任務(wù)或作業(yè)节腐。這是一個(gè)抽象類外盯。你需要繼承它。您的Worker類包含有關(guān)如何執(zhí)行該任務(wù)的信息翼雀,但它沒(méi)有關(guān)于何時(shí)運(yùn)行的信息饱苟。

WorkRequest:它代表了工作調(diào)度請(qǐng)求。每個(gè)工作必須在安排工作之前創(chuàng)建工作請(qǐng)求狼渊。 WorkRequest將包含工作的唯一標(biāo)識(shí)掷空,約束條件說(shuō)明應(yīng)在哪種情況下執(zhí)行任務(wù)。這是一個(gè)抽象類囤锉。該庫(kù)提供了這個(gè)類的兩個(gè)直接子類:OneTimeWorkRequest和PeriodicWorkRequest。

WorkManager:它是基于WorkRequest中定義的約束來(lái)管理和調(diào)度任務(wù)的類护锤。

WorkStatus:這個(gè)類包裝了任何work請(qǐng)求的狀態(tài)官地,你可以通過(guò)唯一的id來(lái)查詢?nèi)魏蝫ork的狀態(tài)。

使用流程

1.創(chuàng)建work

創(chuàng)建一個(gè)Worker的子類烙懦,這個(gè)類有一個(gè)抽象方法doWork()驱入,顧名思義,你需要在后臺(tái)執(zhí)行你想要做的工作氯析,該方法將在后臺(tái)/工作線程中調(diào)用亏较,編寫以執(zhí)行此方法中的任務(wù)。

在返回中掩缓,你必須返回WorkerResult雪情。返回WorkerResult.SUCCESS表明您執(zhí)行的任務(wù)已成功完成。返回WorkerResult.RETRY告訴WorkManager再次重試該工作你辣。返回WorkerResult.FAILURE表示發(fā)生了一個(gè)或多個(gè)錯(cuò)誤巡通。

2.定義約束

定義約束條件以告訴WorkManager合適安排任務(wù)執(zhí)行,如果沒(méi)有提供任何約束條件舍哄,那么該任務(wù)將立即運(yùn)行宴凉。

以下是僅在設(shè)備充電和限制時(shí)才運(yùn)行任務(wù)的約束。

3.創(chuàng)建work request

你可以創(chuàng)建OneTimeWorkRequest來(lái)運(yùn)行一次任務(wù)表悬。

如果你想定期運(yùn)行一個(gè)任務(wù)弥锄,那么創(chuàng)建一個(gè)PeriodicWorkRequest并設(shè)置工作的時(shí)間間隔。

觀察輸出數(shù)據(jù)

Work manager為所有工作請(qǐng)求管理工作狀態(tài)和LiveData,應(yīng)用程序可以觀察到LiveData在工作狀態(tài)發(fā)生變化時(shí)得到通知籽暇。

你也可以通過(guò)調(diào)用getOutputData()來(lái)讀取輸出數(shù)據(jù)温治。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市图仓,隨后出現(xiàn)的幾起案子罐盔,更是在濱河造成了極大的恐慌,老刑警劉巖救崔,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惶看,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡六孵,警方通過(guò)查閱死者的電腦和手機(jī)纬黎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)劫窒,“玉大人本今,你說(shuō)我怎么就攤上這事≈魑。” “怎么了冠息?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孕索。 經(jīng)常有香客問(wèn)我逛艰,道長(zhǎng),這世上最難降的妖魔是什么搞旭? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任散怖,我火速辦了婚禮,結(jié)果婚禮上肄渗,老公的妹妹穿的比我還像新娘镇眷。我一直安慰自己,他們只是感情好翎嫡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布欠动。 她就那樣靜靜地躺著,像睡著了一般惑申。 火紅的嫁衣襯著肌膚如雪翁垂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天硝桩,我揣著相機(jī)與錄音沿猜,去河邊找鬼。 笑死碗脊,一個(gè)胖子當(dāng)著我的面吹牛啼肩,可吹牛的內(nèi)容都是我干的橄妆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼祈坠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼害碾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起赦拘,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慌随,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后躺同,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阁猜,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蹋艺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剃袍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捎谨,死狀恐怖民效,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涛救,我是刑警寧澤畏邢,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站检吆,受9級(jí)特大地震影響棵红,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咧栗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虱肄。 院中可真熱鬧致板,春花似錦、人聲如沸咏窿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)集嵌。三九已至萝挤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間根欧,已是汗流浹背怜珍。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凤粗,地道東北人酥泛。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親柔袁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呆躲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容