带你梳理一遍 Android 核心知识
熱文導讀|???點擊標題閱讀
互聯(lián)網(wǎng)寒冬下,程序員如何突圍提升自己?
Flutter 與 React Native 誰主沉浮?
女面試官:我拉鏈開了你怎么提醒我?
作者:薛定貓的諤
https://juejin.im/post/5c46db4ae51d4503834d8227
超長好文,建議慢慢品用,由于篇幅超越微信限制,略微有點刪減,不影響閱讀。
0、一句話知識點
1. Android 9 (API level 28) 開始廢棄了 Loader API,包括 LoaderManager 和 CursorLoader 等類的使用。推薦使用 ViewModel 和 LiveData 在 Activity 或 Fragment 生命周期中加載數(shù)據(jù);
2. Activity 可以通過 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 保持屏幕常亮,這是最推薦、最簡單、最安全的保持屏幕常亮的方法,給 view 添加 android:keepScreenOn="true" 也是一樣的。
這個只在這個 Activity 生命周期內(nèi)有效,所以大可放心,如果想提前解除常亮,只需要清除這個 flag 即可。
3. WAKE_LOCK 可以阻止系統(tǒng)睡眠,保持 CPU 一直運行,需要 android.permission.WAKE_LOCK 權(quán)限。
通過 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag") 創(chuàng)建實例
通過 wakeLock.acquire() 方法請求鎖,通過 wakelock.release() 釋放鎖
4.?WakefulBroadcastReceiver 結(jié)合 IntentService 也可以阻止系統(tǒng)睡眠;
5. Android 8.0 (API level 26) 開始支持自適應啟動圖標,自適應啟動圖標必須由前景和背景兩部分組成,尺寸必須都是 108 x 108 dp,其中內(nèi)部的 72 x 72 dp 用來顯示圖標,靠近四個邊緣的 18 dp 是保留區(qū)域,用來進行視覺交互
6. 對于字體大小自適應的 TextView 寬和高都不能是 wrap_content,autoSizeTextType 默認是 none,設(shè)置為 uniform 開啟自適應,默認最小 12sp,最大 112sp,粒度 1px。autoSizePresetSizes 屬性可以設(shè)置預置的一些大小
7. Android 8.0 (API level 26) 開始支持 XML 自定義字體,兼容庫可以兼容到 Android 4.1 (API level 16),字體文件路徑為 res/font/,使用屬性為 fontFamily,獲取 Typeface 為 getResources().getFont(R.font.myfont);,兼容庫使用 ResourcesCompat.getFont(context, R.font.myfont)
8. Android 9 (API level 28) 支持控件放大鏡功能,Magnifier 的 show() 方法的參數(shù)是相對于被放大 View 的左上角的坐標
9. 工程中的 Drawable 資源只能有一個狀態(tài),你不應該手動更改它的任何屬性,否則會影響到其它使用這個 Drawable 資源的地方
10. Android 7.0 (API level 24) 開始支持在 XML 文件中使用自定義 Drawable,公共頂級類使用全限定名作為標簽名即可 <com.myapp.MyDrawable>,公共靜態(tài)內(nèi)部類可以使用 class 屬性 class="com.myapp.MyTopLevelClass$MyDrawable"
11. Android 5.0 (API level 21) 開始支持為 Drawable 設(shè)置 tint
12. Android 5.0 (API level 21) 開始支持矢量圖,支持庫可以支持到 Android 2.1 (API level 7+),兼容低版本是需要 Gradle 插件版本大于 2.0+ 時添加 vectorDrawables.useSupportLibrary = true 并使用 VectorDrawableCompat 和 AnimatedVectorDrawableCompat
1、應用資源
1. 添加資源限定符的順序為:?
SIM 卡所屬的國家代碼和移動網(wǎng)代碼 → 語言區(qū)域代碼 → 布局方向 → 最小寬度 → 可用寬度 → 可用高度 → 屏幕大不大 → 屏幕長不長 → 屏幕圓不圓 → 屏幕色域?qū)挷粚?→ 屏幕支持的動態(tài)范圍高不高 → 屏幕方向 → 設(shè)備的 UI 模式 → 夜間模式 → 屏幕像素密度 → 觸摸屏類型 → 鍵盤類型 → 主要的文字輸入方式 ?→ 導航鍵是否可用 → 主要的非觸摸導航方式 → 支持的 API level
2. 一個資源目錄的每種資源限定符最多只能出現(xiàn)一次;
3. 必須提供缺省的資源文件;
4. 資源目錄名是大小寫不敏感的;
5. drawable 資源取別名:
<?xml?version="1.0"?encoding="utf-8"?><resources>????<drawable?name="icon">@drawable/icon_ca</drawable></resources>
<resources>
????<drawable?name="icon">@drawable/icon_ca</drawable>
</resources>
6.?布局文件取別名:
<merge>
????<include?layout="@layout/main_ltr"/>
</merge>
只有動畫、菜單、raw 資源 以及 xml/ 目錄中的資源不能使用別名
7. 尋找使用最優(yōu)資源的流程:
8. 在應用程序運行時,設(shè)備的配置可能會發(fā)生變化(如屏幕方向變化、切換到多窗口模式,切換了系統(tǒng)語言),默認情況下系統(tǒng)會銷毀重建正在運行的 Activity ,所以應用程序必須保證銷毀重建的過程中用戶的數(shù)據(jù)和頁面狀態(tài)完好無損地恢復。
如果不想系統(tǒng)銷毀重建你的 Activity 只需要在 manifest 文件的 <activity> 標簽的 android:configChanges 屬性中添加你想自己處理的配置更改,多個配置使用 "|" 隔開,此時系統(tǒng)就不會在這些配置更改后銷毀重建你的這個 Activity 而是直接調(diào)用它的 onConfigurationChanged() 回調(diào)方法,你需要在這個回調(diào)中自己處理配置更改后的行為。
9. Activity 的銷毀重建不但發(fā)生在設(shè)備配置更改后,只要用戶離開了某個 Activity,那么那個 Activity 就隨時可能被系統(tǒng)銷毀。所以銷毀重建是無法避免的,也不應該逃避,而是應該想辦法保存和恢復狀態(tài)
10. 由于各種各樣的硬件都能安裝 Android 操作系統(tǒng),Android 操作系統(tǒng)之間也可能千差萬別,而應用程序的一些功能是與這些軟硬件息息相關(guān)的,如拍照應用需要設(shè)備必須有攝像頭才能正常工作。
應用可以通過 <uses-feature> 標簽聲明只有滿足這些軟硬件要求的設(shè)備才能安裝,通過它的 android:required 屬性設(shè)置該要求是不是必須的,程序中可以通過 PackageManager.hasSystemFeature() 方法判斷.
2、動態(tài)申請權(quán)限
1. Android 6.0 (API level 23) 開始 targetSdkVersion >= 23 的應用必須在運行時動態(tài)申請權(quán)限
2. 權(quán)限請求對話框是操作系統(tǒng)進行管理的,應用無法也不應該干預。
3. 系統(tǒng)對話框描述的是權(quán)限組而不是某個具體權(quán)限
4. 調(diào)用 requestPermissions() 并不意味著系統(tǒng)一定會彈出權(quán)限請求對話框,也就是說不能假設(shè)調(diào)用該方法后就發(fā)生了用戶交互,因為如果用戶之前勾選了 “禁止后不再詢問” 或者系統(tǒng)策略禁止應用獲取權(quán)限,那么系統(tǒng)會直接拒絕此次權(quán)限請求,沒有任何交互
5. 如果某個權(quán)限跟應用的主要功能無關(guān),如應用中廣告可能需要位置權(quán)限,用戶可能很費解,此時在申請權(quán)限之前彈出對話框向用戶解釋為什么需要這個權(quán)限是個不錯的選擇。但不要在所有申請權(quán)限之前都彈出對話框解釋,因為頻繁地打斷用戶的操作或讓用戶進行選擇容易讓用戶不耐煩
6. Fragment 中的 onRequestPermissionsResult() 方法只有在使用 Fragment#requestPermissions() 方法申請權(quán)限時才可能接收到回調(diào),建議將權(quán)限放在所屬 Activity 中申請和處理
7. 應用應該盡量少地申請權(quán)限,像讓用戶拍一張照片或者選擇一張圖片完全不需要相機權(quán)限和外存權(quán)限,可以通過隱式 Intent 拉起系統(tǒng)相機或其他應用完成,應用只需要在 onActivityResult() 回調(diào)中接收數(shù)據(jù)就行了。但是有一點一定要注意,如果你在 AndroidManifest.xml 文件中聲明了相機權(quán)限,你就必須得動態(tài)申請并獲得相機權(quán)限才能拉起系統(tǒng)相機.
private?void?showContactsWithPermissionsCheck()?{
????if?(ContextCompat.checkSelfPermission(MainActivity.this,
????????????Manifest.permission.READ_CONTACTS)
????????????!=?PackageManager.PERMISSION_GRANTED)?{
????????if?(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
????????????????Manifest.permission.READ_CONTACTS))?{
????????????//?TODO:?彈框解釋為什么需要這個權(quán)限.?【下一步】?->?再次請求權(quán)限
????????}?else?{
????????????ActivityCompat.requestPermissions(MainActivity.this,
????????????????????new?String[]{Manifest.permission.READ_CONTACTS},
????????????????????RC_CONTACTS);
????????}
????}?else?{
????????showContacts();
????}
}
private?void?showContacts()?{
????startActivity(ContactsActivity.getIntent(MainActivity.this));
}
@Override
public?void?onRequestPermissionsResult(int?requestCode,?@NonNull?String[]?permissions,
???????????????????????????????????????@NonNull?int[]?grantResults)?{
????super.onRequestPermissionsResult(requestCode,?permissions,?grantResults);
????switch?(requestCode)?{
????????case?RC_CONTACTS:
????????????if?(grantResults.length?>?0
????????????????????&&?grantResults[0]?==?PackageManager.PERMISSION_GRANTED)?{
????????????????showContacts();
????????????}?else?{
????????????????if?(!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
????????????????????????Manifest.permission.READ_CONTACTS))?{
????????????????????//?TODO:?彈框引導用戶去設(shè)置頁主動授予該權(quán)限.?【去設(shè)置】?->?應用信息頁
????????????????}?else?{
????????????????????//?TODO:?彈框解釋為什么需要這個權(quán)限.?【下一步】?->?再次請求權(quán)限
????????????????}
????????????}
????????????break;
????????default:
????????????break;
????}
}
@Override
protected?void?onActivityResult(int?requestCode,?int?resultCode,?@Nullable?Intent?data)?{
????super.onActivityResult(requestCode,?resultCode,?data);
????if?(requestCode?==?RC_SETTINGS)?{
????????//?TODO:?在用戶主動授予權(quán)限后重新檢查權(quán)限,但不要在這里進行事務(wù)提交等生命周期敏感操作
????}
}
注意看上述代碼中TODO標識。
3、Shortcut
1. 類似于 iOS 的 3D Touch,長按啟動圖標彈出幾個快捷入口,入口最好不要超過 4 個,像搜索、掃描二維碼、發(fā)帖等應用程序最常用功能的入口被稱為靜態(tài) shortcut,不會隨著用戶不同或隨著用戶使用而改變。
還有一種像從某個存檔點繼續(xù)游戲、任務(wù)進度等與用戶相關(guān)的上下文敏感入口被稱為動態(tài) shortcut,會因用戶不同或隨著用戶使用不斷變化。還有一種在 Android 8.0 (API level 26) 及以上系統(tǒng)版本上像固定網(wǎng)頁標簽等用戶主動固定到桌面的快捷方式被稱為固定 shortcut
2. 靜態(tài) shortcut 系統(tǒng)可以自動備份和恢復,動態(tài) shortcut 需要應用自己備份和恢復,固定 shortcut 的圖標系統(tǒng)無法備份和恢復因此需要應用自己完成
3. android:shortcutId 和 android:shortcutShortLabel 屬性是必須的,android:shortcutShortLabel 不能超過 10 個字符,android:shortcutLongLabel 不能超過 25 個字符,android:icon 不能包含 tint
4. 獲取 ShortcutManager 的方式有兩個: getSystemService(ShortcutManager.class) 和 getSystemService(Context.SHORTCUT_SERVICE)
5. 創(chuàng)建固定 shortcut:
if?(mShortcutManager.isRequestPinShortcutSupported())?{
????ShortcutInfo?pinShortcutInfo?=
????????????new?ShortcutInfo.Builder(context,?"my-shortcut").build();
????Intent?pinnedShortcutCallbackIntent?=
????????????mShortcutManager.createShortcutResultIntent(pinShortcutInfo);
????PendingIntent?successCallback?=?PendingIntent.getBroadcast(context,?0,
????????????pinnedShortcutCallbackIntent,?0);
????mShortcutManager.requestPinShortcut(pinShortcutInfo,
????????????successCallback.getIntentSender());
}
4、系統(tǒng)欄適配
1. Android 4.1 (API level 16) 開始可以通過 setSystemUiVisibility() 方法在各個 view 層次中(一般是在 DecorView 中)配置 UI flag 實現(xiàn)系統(tǒng)欄(狀態(tài)欄、導航欄統(tǒng)稱)配置,最終匯總體現(xiàn)到 window 級
2.?View.SYSTEM_UI_FLAG_FULLSCREEN 可以隱藏狀態(tài)欄,View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 可以隱藏導航欄。但是: 用戶的任何交互包括觸摸屏幕都會導致 flag 被清除進而系統(tǒng)欄保持可見,一旦離開當前 Activity flag 就會被清除,所以如果在 onCreate() 方法中設(shè)置了這個 flag 那么按 HOME 鍵再回來狀態(tài)欄又保持可見了。
非要這樣設(shè)置的話一般要放在 onResume() ?或 onWindowFocusChanged() 方法中,而且這樣設(shè)置只有在目標 View 可見時才會生效,狀態(tài)欄/導航欄的顯示隱藏會導致顯示內(nèi)容的大小尺寸跟著變化。
3. View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 可以讓內(nèi)容顯示在狀態(tài)欄后面,View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 可以讓內(nèi)容顯示在導航欄后面,這樣無論系統(tǒng)欄顯示還是隱藏內(nèi)容都不會跟著變化。
但不要讓可交互的內(nèi)容出現(xiàn)在系統(tǒng)欄區(qū)域內(nèi),通過將 android:fitsSystemWindows 屬性設(shè)置為 true 可以讓父容器調(diào)整 padding 以便為系統(tǒng)欄留出空間,如果想自定義這個 padding 可以通過覆寫 View 的 fitSystemWindows(Rect insets) 方法(API level 20 以上覆寫 onApplyWindowInsets(WindowInsets insets) 方法)完成
4. lean back 全屏模式: View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION,隱藏狀態(tài)欄和導航欄,任何交互都會清除 flag 使系統(tǒng)欄保持可見
5. Immersive 全屏模式: View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION,隱藏狀態(tài)欄和導航欄,從被隱藏的系統(tǒng)欄邊緣向內(nèi)滑動會使系統(tǒng)欄保持可見,應用無法響應這個手勢
6. sticky immersive 全屏模式: View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION,隱藏狀態(tài)欄和導航欄,從被隱藏的系統(tǒng)欄邊緣向內(nèi)滑動會使系統(tǒng)欄暫時可見,flag 不會被清除,且系統(tǒng)欄的背景是半透明的,會覆蓋應用的內(nèi)容,應用也可以響應這個手勢,在用戶沒有任何交互或者沒有系統(tǒng)欄交互幾秒鐘后系統(tǒng)欄會自動隱藏
7. 真正的沉浸式全屏體驗需要 6 個 flag:?
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |?
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |?
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |?
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |?
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |?
View.SYSTEM_UI_FLAG_FULLSCREEN
8. 監(jiān)聽系統(tǒng)欄可見性(sticky immersive 全屏模式無法監(jiān)聽):
????@Override
????public?void?onSystemUiVisibilityChange(int?visibility)?{
????????if?((visibility?&?View.SYSTEM_UI_FLAG_FULLSCREEN)?==?0)?{
????????????//?TODO:?The?system?bars?are?visible.?Make?any?desired
????????}?else?{
????????????//?TODO:?The?system?bars?are?NOT?visible.?Make?any?desired
????????}
????}
});
9. 全面屏適配只需要指定支持的最大寬高比即可:?
<meta-data android:name="android.max_aspect" android:value="2.4"/>
10. Android 9 (API level 28) 開始支持劉海屏 cutout 的配置,window 的屬性 layoutInDisplayCutoutMode 默認是 LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,豎屏時可以渲染到劉海區(qū),橫屏時不允許渲染到劉海區(qū)。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 橫豎屏都可以渲染到劉海區(qū)。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 橫豎屏都不允許渲染到劉海區(qū),可以在 values-v28/styles.xml 文件中通過 android:windowLayoutInDisplayCutoutMode 指定默認的劉海區(qū)渲染模式
11. 華為手機通過 <meta-data android:name="android.notch_support" android:value="true" /> 屬性聲明應用是否已經(jīng)適配了劉海屏,如果沒適配,那么在橫屏或者豎屏不顯示狀態(tài)欄時會禁止渲染到劉海區(qū),可以參考: 《華為劉海屏手機安卓O版本適配指導》。
12. 小米手機通過 <meta-data android:name="notch.config" android:value="portrait|landscape" /> 設(shè)置默認的劉海區(qū)渲染模式,開發(fā)者文檔:?
小米劉海屏 Android O 適配
https://dev.mi.com/console/doc/detail?pId=1293
小米劉海屏 Android P 適配?
https://dev.mi.com/console/doc/detail?pId=1341
13. 其他手機的開發(fā)者文檔有: OPPO 手機的?
OPPO凹形屏適配說明
https://open.oppomobile.com/wiki/doc#id=10159
VIVO 手機的?
異形屏應用適配指南?https://dev.vivo.com.cn/documentCenter/doc/103
錘子手機的
Smartisan 開發(fā)者文檔https://resource.smartisan.com/resource/61263ed9599961d1191cc4381943b47a.pdf
14. Android 5.0 (API level 21) 開始支持通過 window 的 setStatusBarColor() 方法設(shè)置狀態(tài)欄背景色,要求 window 必須添加 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 的 flag 并且清除 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 的 flag
15. Android 6.0 (API level 23) 開始可以通過 setSystemUiVisibility() 方法設(shè)置 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR flag 兼容亮色背景的狀態(tài)欄,同樣要求 window 必須添加 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 的 flag 并且清除 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 的 flag
16. 小米手機在 MIUI 開發(fā)版 7.7.13 之前需要通過反射兼容亮色背景的狀態(tài)欄,開發(fā)者文檔:《MIUI 9 & 10“狀態(tài)欄黑色字符”實現(xiàn)方法變更通知》
https://dev.mi.com/console/doc/detail?pId=1159
17. 魅族手機同樣需要通過反射兼容亮色背景的狀態(tài)欄,開發(fā)者文檔:?
《狀態(tài)欄變色》
http://open-wiki.flyme.cn/doc-wiki/index#id?79
5、動畫
view 動畫系統(tǒng)只能作用于 view 對象,只能改變 view 的部分樣式,只是簡單改變了 view 繪制,并沒有改變 view 真正的位置和屬性。核心類是 android.view.animation.Animation 和它的 ScaleAnimation 等子類,一般使用 AnimationUtils.loadAnimation() 方法加載。不建議使用,除非為了方便又能滿足現(xiàn)在和將來的需求
1. 屬性動畫系統(tǒng)是一個健壯的、優(yōu)雅的動畫系統(tǒng),可以對任意對象的屬性做動畫。核心類是 android.animation.Animator 的子類 ValueAnimator、ObjectAnimator、AnimatorSet
2. 通過調(diào)用 ValueAnimator 的 ofInt()、ofFloat() 等工廠方法獲取 ValueAnimator 對象,通過它的 addUpdateListener() 方法可以監(jiān)聽動畫值并在里面進行自定義操作
3. ObjectAnimator 作為 ValueAnimator 的子類可以自動地為目標對象的命名屬性設(shè)置動畫,但是對目標對象有嚴格的要求: 目標對象必須有對應屬性的 setter 方法,如果在工廠方法中只提供了一個動畫值那么它會作為終止值,起始值為目標對象的當前值,此時為了獲取當前屬性值目標對象必須有對應屬性的 getter 方法。有些屬性的更改不會導致 view 重新渲染,此時需要主動調(diào)用 invalidate() 方法強制觸發(fā)重繪
4. AnimatorListenerAdapter 提供了 Animator.AnimatorListener 接口的空實現(xiàn)
5. 多數(shù)情況下可以直接使用系統(tǒng)提供的幾個動畫 duration,如 getResources().getInteger(android.R.integer.config_shortAnimTime)
6. 可以調(diào)用任意 view 對象的 animate() 方法獲取 ViewPropertyAnimator 對象,鏈式調(diào)用這個對象的 scaleX()、alpha() 等方法可以簡單方便地同時對 view 的多個屬性做動畫
7. 為了更好地重用和管理屬性動畫,最好使用 XML 文件來描述動畫并放到 res/animator/ 目錄下,ValueAnimator 對應 <animator> ,ObjectAnimator 對應 <objectAnimator>,AnimatorSet 對應 <set>,使用 AnimatorInflater.loadAnimator() 可以加載這些動畫
8. 動態(tài) Drawable 的實現(xiàn)有兩種,最傳統(tǒng)最簡單的就是像電影關(guān)鍵幀一樣依次指定關(guān)鍵幀和每一幀的停留時間,AnimationDrawable 對應于 XML 文件中的 <animation-list>,保存目錄為 res/drawable/,AnimationDrawable 的 start() 方法可以在 onStart() 中調(diào)用。還有一種是 AnimatedVectorDrawable,需要 res/drawable/ 中的 <animated-vector> 引用 res/drawable/ 中的 <vector> 對其使用 res/animator/ 中的 <objectAnimator> 動畫
9. 突然更改顯示的內(nèi)容會讓視覺感受非常突兀不和諧,而且可能意識不到哪些內(nèi)容突然變了,所以很多場景下需要使用動畫過渡一下,而不是突然更改顯示的內(nèi)容
10. 顯示隱藏 view 的常用動畫有三個: crossfade 動畫,card flip 動畫,circular reveal 動畫
11. crossfade 動畫就是內(nèi)容淡出另一個內(nèi)容淡入交叉進行,也被稱為溶入動畫。實現(xiàn)方式為: 事先將淡入 view 的 visibility 設(shè)置為 GONE → 開始動畫時將淡入 view 的 alpha 設(shè)置為 0,visibility 設(shè)置為 VISIBLE → 將淡入 view 的 alpha 動畫到 1,將淡出 view 的 alpha 動畫到 0 并在動畫結(jié)束時將淡出 view 的 visibility 設(shè)置為 GONE
12. card flip 動畫就是卡片翻轉(zhuǎn)動畫,需要四個動畫描述: card_flip_right_in、card_flip_right_out、card_flip_left_in、card_flip_left_out
13. Android 5.0 (API level 21) 開始支持 circular reveal 圓形裁剪動畫,實現(xiàn)方式為: 事先將 view 的 visibility 設(shè)置為 INVISIBLE → 利用 ViewAnimationUtils.createCircularReveal() 方法創(chuàng)建半徑從 0 到 Math.hypot(cx, cy) 的圓形裁剪動畫 → 將 view 的 visibility 設(shè)置為 VISIBLE 然后開啟動畫
14. 直線動畫移動 view 只需要借助 ObjectAnimator.ofFloat() 方法動畫設(shè)置 view 的 translationX 或 translationY 屬性即可
15. 曲線動畫移動 view 還需要借助 Android 5.0 (API level 21) 開始提供的 PathInterpolator 插值器(對應于 XML 文件中的 <pathInterpolator>),他需要個 Path 對象描述運動的貝塞爾曲線??梢允褂?ObjectAnimator.ofFloat(view, "translationX", 100f) 同時設(shè)置 PathInterpolator 也可以直接設(shè)置 view 動畫路徑 ObjectAnimator.ofFloat(view, View.X, View.Y, path)。系統(tǒng)提供的 fast_out_linear_in.xml、fast_out_slow_in.xml、linear_out_slow_in.xml 三個基礎(chǔ)的曲線插值器可以直接使用
16. 放大預覽動畫只需要同時動畫更改目標 view 的 X,Y,SCALE_X,SCALE_Y 屬性即可,不過要先計算好兩個 view 最終的位置和初始縮放比
17. Android 提供了預加載的布局改變動畫,可以通過 android:animateLayoutChanges="true" 屬性告訴系統(tǒng)開啟默認動畫,或者通過 LayoutTransition API 設(shè)置
18. Activity 內(nèi)部的布局過渡動畫: 過渡動畫框架可以在開始 Scene 和結(jié)束 Scene 開始過渡動畫,Scene 存儲著 view hierarchy 狀態(tài),包括所有 view 和其屬性值,開始 Scene 可以通過 setExitAction() 定義過渡動畫開始前要執(zhí)行的操作,結(jié)束 Scene 可以通過 Scene.setEnterAction() 定義過渡動畫完成后要執(zhí)行的操作。
如果 view hierarchy 是靜態(tài)不變的,可以通過布局文件描述和加載 Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this),否則可以手動創(chuàng)建 new Scene(mSceneRoot, mViewHierarchy)。Transition 的內(nèi)置子類包括 AutoTransition、Fade、ChangeBounds,可以在 res/transition/ 目錄下定義內(nèi)置的 <fade xmlns:android="http://schemas.android.com/apk/res/android" />,多個組合包裹在 <transitionSet> 標簽中,然后使用 TransitionInflater.from(this).inflateTransition(R.transition.fade_transition) 加載。
還可以手動創(chuàng)建 new Fade()。開始過渡動畫時只需要執(zhí)行 TransitionManager.go(mEndingScene, mFadeTransition) 即可。默認是對 Scene 中所有的 view 作動畫,可以通過 addTarget() 或 removeTarget() 在開始過渡動畫前進行調(diào)整。如果不想在兩個 view hierarchy 間進行過渡,而是在同一個 view hierarchy 狀態(tài)更改后執(zhí)行過渡動畫,那就不需要使用 Scene 了,先利用 TransitionManager.beginDelayedTransition(mRootView, mFade) 讓系統(tǒng)記錄 view 的更改,然后增刪 view 來更改 view hierarchy 的狀態(tài),系統(tǒng)會在重繪 UI 時執(zhí)行延遲過渡動畫。
由于 SurfaceView 由非 UI 線程更新,所以它的過渡可能有問題,TextureView 在一些過渡類型上可能有問題,AdapterView 與過渡動畫框架不兼容,TextView 的大小過渡動畫可能有問題
19. Activity 之間的過渡動畫: 需要 Android 5.0 (API level 21) ,內(nèi)置的進入退出過渡動畫包括: explode 從中央進入或退出,slide 從一邊進入或退出,fade 透明度漸變進入或退出。
內(nèi)置的共享元素過渡動畫包括: changeBounds 動態(tài)更改目標 view 的邊界,changeClipBounds 動態(tài)裁剪目標 view 的邊界,changeTransform 動態(tài)更改目標 view 的縮放和旋轉(zhuǎn),changeImageTransform 動態(tài)更改目標 view 的縮放和尺寸。
過渡動畫需要兩個 Activity 都要開啟 window 的內(nèi)容過渡: android:windowActivityTransitions 屬性設(shè)置為 true 或者代碼中手動 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) 開啟。setExitTransition() 和 setSharedElementExitTransition() 方法可以為起始 Activity 設(shè)置退出過渡動畫,setEnterTransition() 和 setSharedElementEnterTransition() 方法可以為目標 Activity 設(shè)置進入過渡動畫。
激活目標 Activity 的時候需要攜帶 ActivityOptions.makeSceneTransitionAnimation(this).toBundle() 的 Bundle,返回的時候要使用 finishAfterTransition() 方法。共享元素需要使用 android:transitionName 屬性或者 View.setTransitionName() 方法指定名字,多個共享元素使用 Pair.create(view1, "agreedName1") 傳遞信息
20. 自定義過渡動畫需要繼承 Transition,實現(xiàn) captureStartValues() 和 captureEndValues() 方法捕獲過渡的 view 屬性值并告訴過渡框架,具體實現(xiàn)為通過 transitionValues.view 檢索當前 view,通過 transitionValues.values.put(PROPNAME_BACKGROUND, view.getBackground()) 存儲屬性值,為了避免沖突 key 的格式必須為 package_name:transition_name:property_name。
同時還要實現(xiàn) createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) 方法,框架調(diào)用這個方法的次數(shù)取決于開始和結(jié)束 scene 需要更改的元素數(shù)
21. 動畫可能會影響性能,必要時可以啟用 Profile GPU Rendering 進行調(diào)試
6、BroadcastReceiver 相關(guān)
1. Android 9 (API level 28) 開始 NETWORK_STATE_CHANGED_ACTION 廣播不再包含 SSID,BSSID 等信息
2. Android 8.0 (API level 26) 開始限制應用靜態(tài)注冊一些非當前應用專屬的隱式廣播的 BroadcastReceiver,免除這項限制的廣播包括 ACTION_LOCKED_BOOT_COMPLETED 等不太可能影響用戶體驗的廣播
3. Android 7.0 (API level 24) 開始不能發(fā)送和接收 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 系統(tǒng)廣播,可以通過 JobInfo 和 JobParameters 完成。不能靜態(tài)注冊 CONNECTIVITY_ACTION 廣播,如果想在網(wǎng)絡(luò)變化時調(diào)度任務(wù)可以選擇使用 WorkManager,如果只在應用運行期間監(jiān)聽網(wǎng)絡(luò)變化使用 ConnectivityManager 比動態(tài)注冊注銷 BroadcastReceiver 更優(yōu)雅
4. 應該盡量在代碼中動態(tài)注冊注銷 BroadcastReceiver
5. onReceive() 方法中不能進行復雜工作否則會導致 ANR,onReceive() 方法一旦執(zhí)行完,系統(tǒng)可能就認為這個廣播接收器已經(jīng)沒用了,隨時會殺掉包含這個廣播接收器的進程,包括這個進程啟動的線程。使用 goAsync() 方法可以在 PendingResult#finish() 方法執(zhí)行前為廣播接收器的存活爭取更多的時間,但最好還是使用 JobScheduler 等方式進行長時間處理工作
6. 使用 sendBroadcast() 方法發(fā)的廣播屬于常規(guī)廣播,所有能接收這個廣播的廣播接收器接收到廣播的順序是不可控的
7. 使用 sendOrderedBroadcast() 方法發(fā)的廣播屬于有序廣播,根據(jù)廣播接收器的優(yōu)先級一個接一個地傳遞這條廣播,相同優(yōu)先級的順序不可控,廣播接收器可以選擇繼續(xù)傳遞給下一個,也可以選擇直接丟掉
8. 使用 LocalBroadcastManager.getInstance(this).sendBroadcast() 方法發(fā)的廣播屬于應用進程內(nèi)的本地廣播,這樣的廣播只有應用自己知道,比系統(tǒng)級的全局廣播更安全更有效率
9. 為了保證廣播的 action 全局唯一,action 的名字最好使用應用的包名作為前綴,最好聲明成靜態(tài)字符串常量.
7、數(shù)據(jù)存儲與共享
分享文件
為了安全地共享文件,分享的文件必須通過 content URI 表示,必須授予這個 content URI 臨時訪問權(quán)限。FileProvider 作為 ContentProvider 的特殊子類,它的 getUriForFile() 靜態(tài)方法可以為文件生成 content URI。
????<meta-data
????????android:name="android.support.FILE_PROVIDER_PATHS"
????????android:resource="@xml/filepaths"?/>
</provider>
????<files-path?path="images/"?name="myimages"?/>
</paths>
android:authorities 屬性一般是以當前應用包名為前綴的字符串,用來標志數(shù)據(jù)的所有者,多個的話用分號隔開
<files-path/> 代表 getFilesDir()
<cache-path/> 代表 getCacheDir()
<external-path/> 代表 Environment.getExternalStorageDirectory()
<external-files-path> 代表 getExternalFilesDir(null)
<external-cache-path> 代表 getExternalCacheDir()
<external-media-path> 代表 getExternalMediaDirs()
File?newFile?=?new?File(imagePath,?"default_image.jpg");
Uri?contentUri?=?FileProvider.getUriForFile(getContext(),?"com.example.myapp.fileprovider",?newFile);
1. 給 Intent 添加 FLAG_GRANT_READ_URI_PERMISSION 或 FLAG_GRANT_WRITE_URI_PERMISSION 的 flag 授予對這個 content URI 的臨時訪問權(quán)限,該權(quán)限會被目標 Activity 所在應用的其它組件繼承,會在所在的任務(wù)結(jié)束時自動撤銷授權(quán)
2. 調(diào)用 Context.grantUriPermission(package, Uri, mode_flags) 方法也可以授予 FLAG_GRANT_READ_URI_PERMISSION 或 FLAG_GRANT_WRITE_URI_PERMISSION 權(quán)限,但只有在主動調(diào)用 revokeUriPermission() 方法后或者重啟系統(tǒng)后才會撤銷授權(quán)
????????PackageManager.MATCH_DEFAULT_ONLY);
if?(activities.size()?>?0)?{
????for?(ResolveInfo?resolveInfo?:?activities)?{
????????grantUriPermission(resolveInfo.activityInfo.packageName,
????????????????outputUri,?Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
????}
}
...
revokeUriPermission(outputUri,?Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
ContentProvider
1. ContentProvider 的數(shù)據(jù)形式和關(guān)系型數(shù)據(jù)庫的表格數(shù)據(jù)類似,因此 API 也像數(shù)據(jù)庫一樣包含增刪改查(CRUD)操作,但為了更好地組織管理一個或多個 ContentProvider,最好通過 ContentResolver 操作 ContentProvider
2. 對于 ContentProvider 的增刪改查操作,不能直接在 UI 線程上執(zhí)行
3. Uri 和 ContentUris 類的靜態(tài)方法可以方便地構(gòu)造 content URI
mCursor?=?getContentResolver().query(
????????UserDictionary.Words.CONTENT_URI,
????????mProjection,
????????mSelectionClause,
????????mSelectionArgs,
????????mSortOrder);
3. 為了防止 SQL 注入,禁止拼接 SQL 語句,如 mSelectionClause 不能直接包含 selectionArgs 參數(shù)值
4. ContentProvider 所在應用本身的組件可以隨便訪問它,不需要授權(quán)
5. 如果 ContentProvider 的應用不指定任何權(quán)限,那么其它應用就無法訪問這個 ContentProvider 的數(shù)據(jù)
6. 使用者需要事先通過 <uses-permission> 標簽獲取訪問權(quán)限
7. 創(chuàng)建 ContentProvider 需要繼承 ContentProvider 并實現(xiàn)增刪改查等一系列方法: onCreate() 在系統(tǒng)創(chuàng)建 provider 后馬上調(diào)用,可以在這里創(chuàng)建數(shù)據(jù)庫,但不要在這里做耗時操作。getType() 返回 content URI 的 MIME 類型。query()、insert()、update()、delete() 進行增刪改查。除了 onCreate() 方法其它方法必須要保證是線程安全的。
8、Notification 相關(guān)
1. Android 5.0 (API level 21) 開始通知可以出現(xiàn)在鎖屏頁面
2. Android 7.0 (API level 24) 開始可以在通知中直接輸入文本或執(zhí)行一些自定義操作,如直接回復按鈕
3. Android 8.0 (API level 26) 開始所有的通知必須屬于一個 channel,channel 被用戶看作是 categories,即通知類別,用戶通過通知類別來精確管理各個應用或一個應用內(nèi)的通知。
一個應用可以有多個通知類別,如私信類別、好友請求類別、應用更新類別等等??梢越o每個通知類別指定通知的 importance,即重要程度,Urgent(緊急)會發(fā)出提示音并在屏幕上彈出通知,High(高)會發(fā)出提示音,Medium(中)不發(fā)出提示音,Low(低)不發(fā)出提示音并且不會出現(xiàn)在狀態(tài)欄中。
在 Android 8.0 (API level 26) 以下的系統(tǒng)中通知的重要程度表現(xiàn)為 priority,即優(yōu)先級。
對應關(guān)系分別為: IMPORTANCE_HIGH 對應 PRIORITY_HIGH 或 PRIORITY_MAX,IMPORTANCE_DEFAULT 對應 PRIORITY_DEFAULT,IMPORTANCE_LOW 對應 PRIORITY_LOW,IMPORTANCE_MIN 對應 PRIORITY_MIN。在應用啟動時可以執(zhí)行下面的代碼創(chuàng)建通知類別,可以無副作用地多次執(zhí)行
????if?(Build.VERSION.SDK_INT?>=?Build.VERSION_CODES.O)?{
????????CharSequence?name?=?getString(R.string.channel_name);
????????String?description?=?getString(R.string.channel_description);
????????int?importance?=?NotificationManager.IMPORTANCE_DEFAULT;
????????NotificationChannel?channel?=?new?NotificationChannel(CHANNEL_ID,?name,?importance);
????????channel.setDescription(description);
????????NotificationManager?notificationManager?=?getSystemService(NotificationManager.class);
????????notificationManager.createNotificationChannel(channel);
????}
}
4. 通過 NotificationChannel 的 enableLights(),setLightColor() 等方法可以指定該通知類別默認的通知行為,但是一旦創(chuàng)建了應用就不能再對它做任何更改了,只有用戶自己可以更改設(shè)置??梢酝ㄟ^ Intent 引導用戶跳轉(zhuǎn)至對應設(shè)置頁
intent.putExtra(Settings.EXTRA_APP_PACKAGE,?getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID,?myNotificationChannel.getId());
startActivity(intent);
5. 查詢用戶當前的通知類別的設(shè)置可以通過 getNotificationChannel()、getNotificationChannels()、getVibrationPattern()、getImportance() 等方法獲取
6. 使用 deleteNotificationChannel(id) 可以刪除通知類別,但是在開發(fā)模式下可能需要重裝應用或者清除數(shù)據(jù)才會完全刪除
7. 通知類別也可以分組
String?groupId?=?"my_group_01";
//?The?user-visible?name?of?the?group.
CharSequence?groupName?=?getString(R.string.group_name);
NotificationManager?mNotificationManager?=
????????(NotificationManager)?getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannelGroup(new?NotificationChannelGroup(groupId,?groupName));
8. Android 5.0 (API level 21) 開始支持勿擾模式(Do Not Disturb)以禁止任何通知產(chǎn)生的聲音和震動。Total silence(完全阻止)會阻止包括鬧鐘視頻游戲在內(nèi)的所有聲音和震動,Alarms only(僅限鬧鐘)會阻止除了鬧鐘外的所有聲音和震動,Priority only(自訂)可以定制要屏蔽的信息通話等系統(tǒng)范圍內(nèi)的通知。setCategory() 方法可以設(shè)置所屬的系統(tǒng)范圍的勿擾類別
9. 每個通知類別可以選擇是否覆蓋勿擾模式的設(shè)置,當勿擾模式設(shè)置為“僅限優(yōu)先事項”時,可以允許繼續(xù)接收此類通知
10. Android 8.1 (API level 27) 開始每秒最多播放一次通知提示音,如果一秒內(nèi)有多個通知那么只播放一秒內(nèi)的第一個通知提示音,如果一秒內(nèi)多次頻繁更新一個通知,那么系統(tǒng)可能會丟棄一些通知更新
11. 最好使用 NotificationCompat 和 NotificationManagerCompat 等兼容庫中的類以便方便地適配低版本系統(tǒng)
12. setSmallIcon() 方法可以設(shè)置小圖標,應用名和時間是由系統(tǒng)設(shè)置的,setLargeIcon() 方法可以設(shè)置右邊大圖標,setContentTitle() 和 setContentText() 方法可以設(shè)置通知的標題和內(nèi)容,setPriority() 方法可以為 Android 8.0 (API level 26) 以下的系統(tǒng)設(shè)置通知優(yōu)先級。系統(tǒng)范圍的預定義通知類別包括 NotificationCompat.CATEGORY_ALARM,NotificationCompat.CATEGORY_REMINDER 等類別,這個類別在勿擾模式中有用,可以通過 setCategory() 方法指定所屬的系統(tǒng)范圍通知類別
13. 默認的通知內(nèi)容會收縮成一行,可以通過 setStyle() 方法設(shè)置其他可展開的通知樣式,
可以設(shè)置大文本塊樣式。
可以設(shè)置多行的 inbox 樣式。
可以設(shè)置消息樣式,但是此樣式會忽略 setContentTitle() 和 setContentText() 方法的設(shè)置,但可以通過 setConversationTitle() 方法設(shè)置該聊天所屬的群組名。
setStyle(new?android.support.v4.media.app.Notification.MediaStyle().setShowActionsInCompactView(1).setMediaSession(mMediaSession.getSessionToken()))?new?android.support.v4.media.app.Notification.MediaStyle().setShowActionsInCompactView(1).setMediaSession(mMediaSession.getSessionToken()))?
可以設(shè)置媒體樣式的通知,屬于 CATEGORY_TRANSPORT 類別。
14. 通知的點擊事件可以通過 setContentIntent() 方法設(shè)置 PendingIntent 對象完成,setAutoCancel(true) 可以在點擊后自動移除通知
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK?|?Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent?pendingIntent?=?PendingIntent.getActivity(this,?0,?intent,?0);
NotificationCompat.Builder?mBuilder?=?new?NotificationCompat.Builder(this,?CHANNEL_ID)
????????.setSmallIcon(R.drawable.notification_icon)
????????.setContentTitle("My?notification")
????????.setContentText("Hello?World!")
????????.setLargeIcon(myBitmap)
????????.setStyle(new?NotificationCompat.BigPictureStyle()
????????????????.bigPicture(myBitmap)
????????????????.bigLargeIcon(null))
????????.setPriority(NotificationCompat.PRIORITY_DEFAULT)
????????.setContentIntent(pendingIntent)
????????.setAutoCancel(true);
15. 通過 NotificationManagerCompat#notify() 方法可以顯示通知,你需要定義一個唯一的 int 值的 ID 作為這個通知的 ID,保存這個 ID 以便之后更新或移除這個通知:
notificationManager.notify(notificationId,?mBuilder.build());
16. setVisibility() 方法可以設(shè)置鎖屏時的通知顯示策略,VISIBILITY_PUBLIC(顯示所有通知)表示完整地顯示通知內(nèi)容,VISIBILITY_SECRET(完全不顯示內(nèi)容)表示不顯示通知的任何信息,VISIBILITY_PRIVATE(隱藏敏感通知內(nèi)容)表示只顯示圖標標題等基本信息
17. NotificationManagerCompat#notify() 方法傳遞之前的通知 ID 可以更新之前的通知,調(diào)用 setOnlyAlertOnce() 方法以便只在第一次出現(xiàn)通知時提示用戶
18. 用戶可以主動清除通知,創(chuàng)建通知時調(diào)用 setAutoCancel() 方法可以在用戶點擊通知后清除通知,創(chuàng)建通知時調(diào)用 setTimeoutAfter() 方法可以在超時后由系統(tǒng)自動清除通知,可以隨時調(diào)用 cancel() 或 cancelAll() 方法清除之前的通知
19. 點擊通知后啟動的 Activity 分為兩種,一種是應用的正常用戶體驗流中的常規(guī) Activity,擁有任務(wù)完整的返回棧。還有一種是僅僅用來展示通知的詳細內(nèi)容的特殊Activity,它不需要返回棧。
對于常規(guī) Activity 需要先通過 android:parentActivityName 屬性或者 android.support.PARENT_ACTIVITY 的 <meta-data> 標簽指定層級關(guān)系,然后:
TaskStackBuilder?stackBuilder?=?TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
PendingIntent?resultPendingIntent?=
????????stackBuilder.getPendingIntent(0,?PendingIntent.FLAG_UPDATE_CURRENT);
對于特殊 Activity 需要先指定 android:taskAffinity="" 和 android:excludeFromRecents="true" 以避免在之前的任務(wù)中啟動,然后:
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK?|?Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent?notifyPendingIntent?=?PendingIntent.getActivity(
????????this,?0,?notifyIntent,?PendingIntent.FLAG_UPDATE_CURRENT
);
9、后臺任務(wù)
1. 每個進程都有一個主線程用來完成任務(wù),一般主線程結(jié)束了那么意味著整個任務(wù)完成了,進程就會自動結(jié)束退出了
2. Android 應用的主線程用來進行測量繪制 UI、協(xié)調(diào)用戶操作、接收生命周期事件等工作,是與用戶的感知直接關(guān)聯(lián)的,所以通常也被叫做 UI 線程,如果在這個線程中做太多工作,那么會導致這個線程掛起或者卡頓,導致糟糕的用戶體驗。所以像解碼 bitmap、讀寫磁盤、執(zhí)行網(wǎng)絡(luò)請求等需要長時間計算和處理的操作都應該放到單獨的后臺線程中去做
3. 后臺線程雖然是用戶感覺不到的,但通常卻是最消耗系統(tǒng)資源的,有的線程大部分時間都在占用 CPU 完成復雜的計算,我們管這種稱為 CPU 密集型操作,有的線程大部分時間都在進行 I/O 的讀寫操作,我們管這種叫做 I/O 密集型操作。我們可以根據(jù)不同的操作類型選擇不同的策略來處理以便最大化系統(tǒng)的吞吐量同時最小化所需代價。同時長時間運行的后臺線程也加劇了電量的消耗,所以不管是操作系統(tǒng)還是開發(fā)者都需要 對這些后臺線程的行為進行限制
4. 在創(chuàng)建一個后臺任務(wù)之前,我們需要先要對它分析一下,它是要馬上執(zhí)行還是可以延遲執(zhí)行?它需要系統(tǒng)滿足指定條件才能執(zhí)行嗎?它需要在精確的時間點執(zhí)行嗎?
WorkManager
1. 通過 WorkManager 可以優(yōu)雅地執(zhí)行 可延遲執(zhí)行的 異步任務(wù),當應用退出后仍然可以繼續(xù)執(zhí)行,當滿足系統(tǒng)條件(聯(lián)網(wǎng)、充電、重啟)時仍然可以觸發(fā)任務(wù)的執(zhí)行
2. 特別適合用來向后臺發(fā)送日志或分析數(shù)據(jù),或者用來周期性的與服務(wù)器同步數(shù)據(jù)
3. WorkManager 在 Android 6.0 (API level 23) 及以上系統(tǒng)上借助 JobScheduler 實現(xiàn),在之前的系統(tǒng)上借助 BroadcastReceiver 和 AlarmManager 實現(xiàn)
4. WorkManager 可以對任務(wù)添加網(wǎng)絡(luò)條件和充電狀態(tài)等條件限制,可以調(diào)度一次性的或周期性的任務(wù),可以監(jiān)聽和管理被調(diào)度的任務(wù),可以將多個任務(wù)連在一起
5. 一次性的任務(wù)可以使用 OneTimeWorkRequest,周期性的任務(wù)使用 PeriodicTimeWorkRequest
6. 如果指定了多個限制,那么只有在所有限制都滿足的情況下任務(wù)才會執(zhí)行:
????.setRequiresDeviceIdle(true)
????.setRequiresCharging(true)
?????.build();
OneTimeWorkRequest?compressionWork?=
????????????????new?OneTimeWorkRequest.Builder(CompressWorker.class)
?????.setConstraints(constraints)
?????.build();
7. 任務(wù)交給系統(tǒng)后可能會馬上被執(zhí)行,可以通過 setInitialDelay(10, TimeUnit.MINUTES) 設(shè)置一個最小延時
8. 如果需要重試任務(wù)可以在 Worker 中使用 Result.retry() 完成,采用的補償策略默認是 EXPONENTIAL 指數(shù)級的,可以使用 setBackoffCriteria() 方法調(diào)整策略
9. 可以通過 setInputData() 方法為任務(wù)設(shè)置輸入數(shù)據(jù),在 Worker 中可以通過 getInputData() 方法獲取到輸入數(shù)據(jù),Result.success() 和 Result.failure() 可以攜帶輸出數(shù)據(jù)。數(shù)據(jù)應該盡可能的簡單,不能超過 10KB
10. addTag 方法可以給任務(wù)打 Tag,然后就可以使用 WorkManager.cancelAllWorkByTag(String) 和 WorkManager.getWorkInfosByTagLiveData(String) 等方法方便操作任務(wù)了
11. 如果一個任務(wù)的先決任務(wù)沒有完成那么會被認為是 BLOCKED 態(tài)
12. 如果任務(wù)的限制和定時滿足要求那么會被認為是 ENQUEUED 態(tài)
13. 如果任務(wù)正在執(zhí)行那么會被認為是 RUNNING 態(tài)
14. 如果任務(wù)返回了 Result.success() 那么會被認為是 SUCCEEDED 態(tài),這是最終態(tài),只有 OneTimeWorkRequest 可能進入這個狀態(tài)
15. 如果任務(wù)返回了 Result.failure() 那么會被認為是 FAILED 態(tài),這是最終態(tài),只有 OneTimeWorkRequest 可能進入這個狀態(tài),所有相關(guān)的任務(wù)也會被標記為 FAILED 且不會被執(zhí)行
16. 顯式取消一個沒終止的 WorkRequest 會被認為是 CANCELLED 態(tài),所有相關(guān)的任務(wù)也會被標記為 CANCELLED 且不會被執(zhí)行
17. WorkManager.getWorkInfoById(UUID) 和 WorkManager.getWorkInfoByIdLiveData(UUID) 等方法可以定位想要的任務(wù)進行觀察
18. 可以將任務(wù)連在一起:
????.beginWith(Arrays.asList(filter1,?filter2,?filter3))
????.then(compress)
????.then(upload)
????.enqueue();
Foreground service
對于用戶觸發(fā)的必須馬上執(zhí)行且必須執(zhí)行完的后臺任務(wù),需要使用 Foreground services 實現(xiàn),它既告訴系統(tǒng)這個應用正在執(zhí)行重要的任務(wù)不能被殺掉,又通過通知欄告訴用戶有后臺工作正在執(zhí)行.
AlarmManager
如果任務(wù)需要在精確的時間點執(zhí)行,可以使用 AlarmManager
DownloadManager
如果需要執(zhí)行一個長時間的 HTTP 下載任務(wù),可以使用 DownloadManager。
DownloadManager 獨立于應用之外,可以在下載失敗、更改網(wǎng)絡(luò)連接、系統(tǒng)重啟后進行重試
public?static?long?downloadApk(String?url,?String?title,?String?desc)?{????DownloadManager.Request?request?=?new?DownloadManager.Request(Uri.parse(url));????request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE?|?DownloadManager.Request.NETWORK_WIFI)????????????.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)????????????.setTitle(title)????????????.setDescription(desc)????????????.setDestinationInExternalFilesDir(MyApplication.getInstance(),?null,?"apks")????????????.allowScanningByMediaScanner();????DownloadManager?downloadManager?=?(DownloadManager)?MyApplication.getInstance().getSystemService(Context.DOWNLOAD_SERVICE);????return?downloadManager.enqueue(request);}String?url,?String?title,?String?desc)?{
????DownloadManager.Request?request?=?new?DownloadManager.Request(Uri.parse(url));
????request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE?|?DownloadManager.Request.NETWORK_WIFI)
????????????.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
????????????.setTitle(title)
????????????.setDescription(desc)
????????????.setDestinationInExternalFilesDir(MyApplication.getInstance(),?null,?"apks")
????????????.allowScanningByMediaScanner();
????DownloadManager?downloadManager?=?(DownloadManager)?MyApplication.getInstance().getSystemService(Context.DOWNLOAD_SERVICE);
????return?downloadManager.enqueue(request);
}
更多學習和討論,歡迎加入我們的知識星球,這里有1000+小伙伴,讓你的學習不寂寞~·
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
我們的知識星球第三期開期了,已達到1100人了,能連續(xù)做三期已很不容易了,有很多老用戶續(xù)期,目前續(xù)期率達到50%,說明了大家對我們的知識星球還是很認可的,歡迎大家加入盡早我們的知識星球,更多星球信息參見:
歡迎加入Java和Android架構(gòu)社群
如何進階成為Java的Android版和架構(gòu)師?
說兩件事
微信掃描或者點擊上方二維碼領(lǐng)取的Android \ Python的\ AI \的Java等高級進階資源
更多學習資料點擊下面的“閱讀原文?”獲取
謝謝老板,點個好看↓
總結(jié)
以上是生活随笔為你收集整理的带你梳理一遍 Android 核心知识的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为交换机打补丁
- 下一篇: Android 实现汉字转成拼音,实现L