main()
函數,由於應用是由多個組件拼湊在一塊兒的,每一個組件都是系統或者用戶進入應用的入口,組件之間既能夠是相互獨立的,也能夠是相互依賴的。系統和其它應用在被容許的狀況下能夠啓動/激活一個應用的任意一個組件Activity
,Service
,BroadcastReceiver
和 ContentProvider
Activity
表示一個新的用戶界面,只能由系統進行建立和銷燬,應用只能監聽到一些生命週期回調,這些回調一般也被叫做生命週期方法Activity
的名字一旦肯定好就不要再更改了,不然可能會引起一系列問題Service
表示一個後臺服務,Service
能夠是獨立的,能夠在應用退出後繼續運行。也能夠綁定到其餘進程或 Activity
,表示其餘進程想使用這個 Service
,像輸入法、動態壁紙、屏保等系統功能都是以 Service
的形式存在的,在須要運行的時候進行綁定JobScheduler
,由於 JobScheduler
和 Doze
API 配合下通常會比簡單使用 Service
更省電BroadcastReceiver
是一個事件傳遞的組件,經過它應用能夠響應系統範圍的廣播通知。系統的包管理器會在安裝應用時將應用中的靜態廣播接收器註冊好,因此即便應用沒在運行,系統也能把事件傳遞到該組件。BroadcastReceiver
能夠實現進程間通訊ContentProvider
是在多個應用間共享數據的組件,若是應用的一些數據想要被其它應用使用,必須經過 ContentPrivider
進行管理,不過應用的私有數據也能夠經過 ContentProvider
進行管理,主要仍是由於 ContentProvider
提供了共享數據的抽象,使用者不須要知道數據到底是以文件形式仍是數據庫等其餘形式存儲的,只須要經過 ContentProvider
提供的 統一的 API 進行數據的增刪改查便可。同時 ContentProvider
還提供了 安全 環境,能夠根據須要方便地控制數據的訪問權限,不須要手動控制文件權限或數據庫權限ContentResolver
操做 ContentProvider
ContentProvider
能夠實現進程間通訊Activity
,Service
,BroadcastReceiver
都須要經過被稱爲 Intent
的異步消息激活Intent
形式的ContentProvider
只有在收到 ContentResolver
的請求時纔會被激活BroadcastReceiver
能夠不在 manifest 文件中註冊,由於有些 BroadcastReceiver
須要在程序運行時動態地註冊和註銷。而其它組件必須在 manifest 文件中註冊,不然沒法被系統記錄,也就沒法被激活Intent
經過組件類名顯式指明瞭惟一的目標組件,那麼這個 Intent
就是顯式的,不然就是隱式的。隱式 Intent
通常只描述要執行動做的類型,必要時能夠攜帶數據,系統會根據這個隱式 Intent
的描述決定激活哪一個組件,若是有多個組件符合激活條件,系統通常會彈出選擇框讓用戶選擇到底激活哪一個組件Service
必須使用顯式 Intent
激活,不能聲明 IntentFilter
Activity
使用顯式 Intent
,啓動隨便一個能完成指定工做的 Activity
使用隱式 Intent
。能完成指定工做的那些想要被隱式 Intent
激活的 Activity
須要事先聲明好 IntentFilter
表示本身有能力處理什麼工做,IntentFilter
通常經過 能完成的動做 、意圖類型 和 額外數據 來描述Intent
激活,意圖類型至少要包含 android.intent.category.DEFAULT
的意圖類型Intent
激活 Activity
以前必定要檢查一下有沒有 Activity
能處理這個 Intent
:if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
複製代碼
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
複製代碼
Intent
時每次都強制用戶選擇一個組件激活:Intent intent = new Intent(Intent.ACTION_SEND);
String title = getResources().getString(R.string.chooser_title);
Intent chooser = Intent.createChooser(intent, title);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
複製代碼
Activity
能被隱式 Intent
激活,若是想要某個 連接 能直接跳轉到你的 Activity
,必須配置好 IntentFilter
。這種連接分爲兩種: Deep links 和 Android App Linksandroid.intent.action.VIEW
的 action 以便 Google Search 能直接打開,須要 android.intent.category.DEFAULT
的 category 才能響應隱式 Intent,須要 android.intent.category.BROWSABLE
的 category 瀏覽器打開連接時才能跳轉到應用,因此經典用例以下。一個 intent filter 最好只聲明一個 data 描述,不然你得考慮和測試全部變體的狀況。系統處理這個連接的流程爲: 若是用戶以前指定了打開這個連接的默認應用就直接打開這個應用 → 若是隻有一個應用能夠處理這個連接就直接打開這個應用 → 彈窗讓用戶選擇用哪一個應用打開<activity android:name="com.example.android.GizmosActivity" android:label="@string/title_gizmos" >
<intent-filter android:label="@string/filter_view_http_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "http://www.example.com/gizmos」 -->
<data android:scheme="http" android:host="www.example.com" android:pathPrefix="/gizmos" />
<!-- note that the leading "/" is required for pathPrefix-->
</intent-filter>
<intent-filter android:label="@string/filter_view_example_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "example://gizmos」 -->
<data android:scheme="example" android:host="gizmos" />
</intent-filter>
</activity>
複製代碼
<intent-filter>
標籤的 android:autoVerify
設置爲 true
以告訴系統自動驗證你的應用屬於這個 HTTP URL 域名 → 填寫好網站域名和應用 ID 並使用簽名文件生成 Digital Asset Links JSON 文件 → 將文件上傳到服務器,訪問路徑爲 https://domain.name/.well-known/assetlinks.json
,響應格式爲 application/json
,子域名也須要存在對應的文件,一個域名能夠關聯多個應用,一個應用也能夠關聯多個域名,且可使用相同的簽名 → 利用編輯器插件完成關聯並驗證Intent#parseUri()
方法,獲取的 intent 必須嚴格過濾,intent 至少包含 addCategory(「android.intent.category.BROWSABLE」)
,setComponent(null)
,setSelector(null)
3 個策略<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable name="icon">@drawable/icon_ca</drawable>
</resources>
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<merge>
<include layout="@layout/main_ltr"/>
</merge>
複製代碼
Activity
,因此應用程序必須保證銷燬重建的過程當中用戶的數據和頁面狀態無缺無損地恢復。若是不想系統銷燬重建你的 Activity
只須要在 manifest 文件的 <activity>
標籤的 android:configChanges
屬性中添加你想本身處理的配置更改,多個配置使用 "|
" 隔開,此時系統就不會在這些配置更改後銷燬重建你的這個 Activity
而是直接調用它的 onConfigurationChanged()
回調方法,你須要在這個回調中本身處理配置更改後的行爲。Activity
的銷燬重建不但發生在設備配置更改後,只要用戶離開了某個 Activity
,那麼那個 Activity
就隨時可能被系統銷燬。因此銷燬重建是沒法避免的,也不該該逃避,而是應該想辦法保存和恢復狀態<uses-feature>
標籤聲明只有知足這些軟硬件要求的設備才能安裝,經過它的 android:required
屬性設置該要求是否是必須的,程序中能夠經過 PackageManager.hasSystemFeature()
方法判斷Activity
變得對用戶可見時,將會回調 onStart()
, 當 Activity
變得能夠和用戶交互時,將會回調 onResume()
onPause()
被調用時 Activity
可能依然對用戶所有可見,如多窗口模式下沒有得到焦點時,因此在 onResume()
中申請資源在 onPause()
中釋放資源的想法並不老是合理的onStop()
被調用時表示 Activity
已經徹底不可見了,此時應該儘可能中止包含動畫在內的 UI 更新,儘可能釋放暫時不用的資源。對於 stopped 的 Activity
,系統隨時可能殺掉包含這個 Activity
的進程,若是沒有合適的機會能夠在 onStop()
中保存一些數據Activity
(殺掉了該 Activity
實例所在的進程),那麼系統確定記得這個實例存在過,在用戶從新回到這個 Activity
時會從新建立一個新的實例,並將以前保存好的實例狀態傳遞給這個新的實例。這個系統以前保存好的用來恢復 Activity
狀態的數據被稱爲實例狀態(Instance state),實例狀態是以鍵值對的形式存儲在 Bundle 對象中的,默認系統只能自動存儲和恢復有 ID 的 View 的簡單狀態(如輸入框的文本,滾動控件的滾動位置),但因爲在主線程中序列化或反序列化 Bundle
對象既消耗時間又消耗系統進程內存,因此最好只用它保存簡單、輕量的數據onSaveInstanceState()
被調用的時機: 對於 Build.VERSION_CODES.P
及以後的系統該方法會在 onStop()
以後隨時可能被調用,對於以前的系統該方法會在 onStop()
以前隨時被調用onRestoreInstanceState()
被調用的時機: 若是有實例狀態要恢復那麼必定會在 onStart()
以後被調用onActivityResult()
被調用時機: onResume()
以前。目標 Activity
沒有顯式返回任何結果或者崩潰那麼 resultCode 就會是 RESULT_CANCELED
Activity#onCreate()
方法中提交事務是沒問題的,由於你能夠在裏面根據保存的狀態重建,可是在其餘生命週期回調中提交事務就可能會出現問題了。FragmentActivity#onPostResume()
方法中調用了 FragmentActivity#onResumeFragments()
方法完成其關聯的全部的 Fragment 的 resume 事件的分發,執行完這兩個方法 Activity 和它關聯的全部 Fragment 纔算真正的 resumed,纔算恢復了狀態,才能夠提交事務,因此若是非要在 Activity#onCreate()
以外的回調中提交事務那麼 FragmentActivity#onPostResume()
和 FragmentActivity#onResumeFragments()
是最好的選擇。避免在異步的回調中提交事務: 由於在這些回調執行的時候很難肯定當前 Activity 正處於什麼生命週期狀態,並且忽然地提交事務更改大量 UI 會產生糟糕的用戶體驗,因此若是遇到這樣的場景能夠考慮換一種實現思路,不要隨便使用 commitAllowingStateLoss()
方法Activity
能夠在 manifest 文件中定義本身應該如何與當前任務相關聯,Activity
也能夠在啓動其它 Activity
時經過 Intent
的 flag 要求其它 Activity
應該如何與當前任務相關聯,若是二者同時出現,那麼 Intent
的 flag 要求獲勝launchMode
屬性默認是 standard
,每次啓動這樣的 Activity
都會新建一個新的實例放入啓動它的任務中。一個新的 Intent 總會建立一個新的實例。一個任務能夠有多個該 Activity 的實例,每一個該 Activity 的實例能夠屬於不一樣的任務launchMode
屬性是 singleTop
的 Activity
: 若是當前任務頂部已是這個 Activity
的實例那麼就直接將 Intent
傳遞給這個實例的 onNewIntent()
方法。一個任務能夠有多個該 Activity 的實例,每一個該 Activity 的實例能夠屬於不一樣的任務launchMode
屬性是 singleTask
的 Activity
: 若是這個 Activity
的實例已經在某個任務中存在了那麼就直接將 Intent
傳遞給這個實例的 onNewIntent()
方法,並將其所在的任務移到前臺即當前任務頂部,不然會新建一個任務並實例化一個這個 Activity
的實例放在棧底launchMode
屬性是 singleInstance
的 Activity
: 和 singleTask
相似,不過它會保證新的任務中有且僅有一個這個 Activity
的實例FLAG_ACTIVITY_NEW_TASK
: 行爲和 singleTask
同樣,不過在新建任務以前會先尋找是否已經存在和這個 Activity
有相同 affinity 的任務,若是已經存在就不新建任務了,而是直接在那個任務中啓動FLAG_ACTIVITY_SINGLE_TOP
: 行爲和 singleTop
同樣FLAG_ACTIVITY_CLEAR_TOP
: 若是當前任務中已經有要啓動的 Activity
的實例了,那麼就銷燬它上面全部的 Activity
(甚至包括它本身),因爲 launchMode
屬性是 standard
的 Activity
一個新的 Intent 總會建立一個新的實例,因此若是要啓動的 Activity
的 launchMode
屬性是 standard
的而且沒有 FLAG_ACTIVITY_SINGLE_TOP
的 flag,那麼這個 flag 會銷燬它本身而後建立一個新的實例FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
結合使用能夠直接定位指定的 Activity
到前臺Activity
是在當前任務中啓動仍是在新任務中啓動,點擊返回鍵均可以直接或間接回到以前的 Activity
,間接的狀況像 singleTask
是將整個任務而不是隻有一個 Activity
移到前臺,任務中的全部的 Activity
在點擊返回鍵的時候都要依次彈出Activity
外的的全部 Activity
。將最底層 Activity
的 <activity>
標籤的 alwaysRetainTaskState
屬性設置爲 true
能夠保留任務中全部的 Activity
。將最底層 Activity
的 <activity>
標籤的 clearTaskOnLaunch
屬性設置爲 true
能夠在不管什麼時候進入或離開這個任務都清除任務中除了最底層 Activity
外的的全部 Activity
。包含最底層 Activity
在內的任何 Activity
只要 finishOnTaskLaunch
屬性設置爲 true
那麼離開任務再回來都不會出現了Activity
做爲新文檔添加到最近任務中須要設置 newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
且 launchMode
必須是 standard
的,若是此時又設置了 newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
那麼系統每次都會建立新的任務並將目標 Activity
做爲根 Activity
,若是沒有設置 FLAG_ACTIVITY_MULTIPLE_TASK
,那麼 Activity
實例會被重用到新的任務中(若是已經存在這樣的任務就不會重建,而是直接將任務移到前臺並調用 onNewIntent()
)<activity>
標籤的 android:documentLaunchMode
屬性默認是 none
: 不會爲新文檔建立新的任務。intoExisting
與設置了 FLAG_ACTIVITY_NEW_DOCUMENT
但沒設置 FLAG_ACTIVITY_MULTIPLE_TASK
同樣。always
與設置了 FLAG_ACTIVITY_NEW_DOCUMENT
同時設置了 FLAG_ACTIVITY_MULTIPLE_TASK
同樣。never
和 none
同樣不過會覆蓋 FLAG_ACTIVITY_NEW_DOCUMENT
和 FLAG_ACTIVITY_MULTIPLE_TASK
Intent.FLAG_ACTIVITY_NEW_DOCUMENT|android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
同時 <activity>
標籤的 android:autoRemoveFromRecents
屬性設置爲 false
可讓文檔 Activity
即便結束了也能夠保留在最近任務中finishAndRemoveTask()
方法能夠移除當前任務requestPermissions()
並不意味着系統必定會彈出權限請求對話框,也就是說不能假設調用該方法後就發生了用戶交互,由於若是用戶以前勾選了 「禁止後再也不詢問」 或者系統策略禁止應用獲取權限,那麼系統會直接拒絕這次權限請求,沒有任何交互Fragment
中的 onRequestPermissionsResult()
方法只有在使用 Fragment#requestPermissions()
方法申請權限時纔可能接收到回調,建議將權限放在所屬 Activity
中申請和處理onActivityResult()
回調中接收數據就好了。可是有一點必定要注意,若是你在 AndroidManifest.xml
文件中聲明瞭相機權限,你就必須得動態申請並得到相機權限才能拉起系統相機// 請求通信錄權限的模板代碼以下
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: 彈框解釋爲何須要這個權限. 【下一步】 -> 再次請求權限
} 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: 彈框引導用戶去設置頁主動授予該權限. 【去設置】 -> 應用信息頁
} else {
// TODO: 彈框解釋爲何須要這個權限. 【下一步】 -> 再次請求權限
}
}
break;
default:
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SETTINGS) {
// TODO: 在用戶主動授予權限後從新檢查權限,但不要在這裏進行事務提交等生命週期敏感操做
}
}
複製代碼
android:shortcutId
和 android:shortcutShortLabel
屬性是必須的,android:shortcutShortLabel
不能超過 10 個字符,android:shortcutLongLabel
不能超過 25 個字符,android:icon
不能包含 tintShortcutManager
的方式有兩個: getSystemService(ShortcutManager.class)
和 getSystemService(Context.SHORTCUT_SERVICE)
ShortcutManager mShortcutManager =
context.getSystemService(ShortcutManager.class);
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());
}
複製代碼
Parcelable
對象用來在進程間、Activity
間傳遞數據,保存實例狀態也是用它,Bundle
是它的一個實現,最好只用它存儲和傳遞少許數據,別超過 50k,不然既可能影響性能又可能致使崩潰Loader
API,包括 LoaderManager
和 CursorLoader
等類的使用。推薦使用 ViewModel
和 LiveData
在 Activity
或 Fragment
生命週期中加載數據Activity
能夠經過 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
保持屏幕常亮,這是最推薦、最簡單、最安全的保持屏幕常亮的方法,給 view 添加 android:keepScreenOn="true"
也是同樣的。這個只在這個 Activity
生命週期內有效,因此大可放心,若是想提早解除常亮,只須要清除這個 flag 便可WAKE_LOCK
能夠阻止系統睡眠,保持 CPU 一直運行,須要 android.permission.WAKE_LOCK
權限,經過 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag")
建立實例,經過 wakeLock.acquire()
方法請求鎖,經過 wakelock.release()
釋放鎖WakefulBroadcastReceiver
結合 IntentService
也能夠阻止系統睡眠setSystemUiVisibility()
方法在各個 view 層次中(通常是在 DecorView 中)配置 UI flag 實現系統欄(狀態欄、導航欄統稱)配置,最終彙整體現到 window 級View.SYSTEM_UI_FLAG_FULLSCREEN
能夠隱藏狀態欄,View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
能夠隱藏導航欄。可是: 用戶的任何交互包括觸摸屏幕都會致使 flag 被清除進而系統欄保持可見,一旦離開當前 Activity
flag 就會被清除,因此若是在 onCreate()
方法中設置了這個 flag 那麼按 HOME 鍵再回來狀態欄又保持可見了,非要這樣設置的話通常要放在 onResume()
或 onWindowFocusChanged()
方法中,並且這樣設置只有在目標 View 可見時纔會生效,狀態欄/導航欄的顯示隱藏會致使顯示內容的大小尺寸跟着變化。View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
可讓內容顯示在狀態欄後面,View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
可讓內容顯示在導航欄後面,這樣不管系統欄顯示仍是隱藏內容都不會跟着變化,但不要讓可交互的內容出如今系統欄區域內,經過將 android:fitsSystemWindows
屬性設置爲 true
可讓父容器調整 padding 以便爲系統欄留出空間,若是想自定義這個 padding 能夠經過覆寫 View 的 fitSystemWindows(Rect insets)
方法(API level 20 以上覆寫 onApplyWindowInsets(WindowInsets insets)
方法)完成View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
,隱藏狀態欄和導航欄,任何交互都會清除 flag 使系統欄保持可見View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
,隱藏狀態欄和導航欄,從被隱藏的系統欄邊緣向內滑動會使系統欄保持可見,應用沒法響應這個手勢View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
,隱藏狀態欄和導航欄,從被隱藏的系統欄邊緣向內滑動會使系統欄暫時可見,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
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@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
}
}
});
複製代碼
<meta-data android:name="android.max_aspect" android:value="2.4"/>
layoutInDisplayCutoutMode
默認是 LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
,豎屏時能夠渲染到劉海區,橫屏時不容許渲染到劉海區。LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
橫豎屏均可以渲染到劉海區。LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
橫豎屏都不容許渲染到劉海區,能夠在 values-v28/styles.xml
文件中經過 android:windowLayoutInDisplayCutoutMode
指定默認的劉海區渲染模式<meta-data android:name="android.notch_support" android:value="true" />
屬性聲明應用是否已經適配了劉海屏,若是沒適配,那麼在橫屏或者豎屏不顯示狀態欄時會禁止渲染到劉海區,開發者文檔: 《華爲劉海屏手機安卓O版本適配指導》<meta-data android:name="notch.config" android:value="portrait|landscape" />
設置默認的劉海區渲染模式,開發者文檔: 《小米劉海屏 Android O 適配》,《小米劉海屏 Android P 適配》setStatusBarColor()
方法設置狀態欄背景色,要求 window 必須添加 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
的 flag 而且清除 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
的 flagsetSystemUiVisibility()
方法設置 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flag 兼容亮色背景的狀態欄,一樣要求 window 必須添加 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
的 flag 而且清除 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
的 flagandroid.view.animation.Animation
和它的 ScaleAnimation
等子類,通常使用 AnimationUtils.loadAnimation()
方法加載。不建議使用,除非爲了方便又能知足如今和未來的需求android.animation.Animator
的子類 ValueAnimator
、ObjectAnimator
、AnimatorSet
ValueAnimator
的 ofInt()
、ofFloat()
等工廠方法獲取 ValueAnimator
對象,經過它的 addUpdateListener()
方法能夠監聽動畫值並在裏面進行自定義操做ObjectAnimator
做爲 ValueAnimator
的子類能夠自動地爲目標對象的命名屬性設置動畫,可是對目標對象有嚴格的要求: 目標對象必須有對應屬性的 setter 方法,若是在工廠方法中只提供了一個動畫值那麼它會做爲終止值,起始值爲目標對象的當前值,此時爲了獲取當前屬性值目標對象必須有對應屬性的 getter 方法。有些屬性的更改不會致使 view 從新渲染,此時須要主動調用 invalidate()
方法強制觸發重繪AnimatorListenerAdapter
提供了 Animator.AnimatorListener
接口的空實現getResources().getInteger(android.R.integer.config_shortAnimTime)
animate()
方法獲取 ViewPropertyAnimator
對象,鏈式調用這個對象的 scaleX()
、alpha()
等方法能夠簡單方便地同時對 view 的多個屬性作動畫res/animator/
目錄下,ValueAnimator
對應 <animator>
,ObjectAnimator
對應 <objectAnimator>
,AnimatorSet
對應 <set>
,使用 AnimatorInflater.loadAnimator()
能夠加載這些動畫AnimationDrawable
對應於 XML 文件中的 <animation-list>
,保存目錄爲 res/drawable/
,AnimationDrawable
的 start()
方法能夠在 onStart()
中調用。還有一種是 AnimatedVectorDrawable
,須要 res/drawable/
中的 <animated-vector>
引用 res/drawable/
中的 <vector>
對其使用 res/animator/
中的 <objectAnimator>
動畫GONE
→ 開始動畫時將淡入 view 的 alpha 設置爲 0,visibility 設置爲 VISIBLE
→ 將淡入 view 的 alpha 動畫到 1,將淡出 view 的 alpha 動畫到 0 並在動畫結束時將淡出 view 的 visibility 設置爲 GONE
card_flip_right_in
、card_flip_right_out
、card_flip_left_in
、card_flip_left_out
INVISIBLE
→ 利用 ViewAnimationUtils.createCircularReveal()
方法建立半徑從 0 到 Math.hypot(cx, cy)
的圓形裁剪動畫 → 將 view 的 visibility 設置爲 VISIBLE
而後開啓動畫ObjectAnimator.ofFloat()
方法動畫設置 view 的 translationX
或 translationY
屬性便可PathInterpolator
插值器(對應於 XML 文件中的 <pathInterpolator>
),他須要個 Path
對象描述運動的貝塞爾曲線。可使用 ObjectAnimator.ofFloat(view, "translationX", 100f)
同時設置 PathInterpolator
也能夠直接設置 view 動畫路徑 ObjectAnimator.ofFloat(view, View.X, View.Y, path)
。系統提供的 fast_out_linear_in.xml
、fast_out_slow_in.xml
、linear_out_slow_in.xml
三個基礎的曲線插值器能夠直接使用support-dynamic-animation
支持庫,最多見的就是 FlingAnimation
和 SpringAnimation
動畫,物理動畫主要是模擬現實生活中的物理世界,利用經典物理學的知識和原理實現動畫過程,其中最關鍵的就是力的概念。FlingAnimation
就是用戶經過手勢給動畫元素一個力,動畫元素在這個力的做用下運動,以後因爲摩擦力的存在慢慢減速直到結束,固然這個力也能夠經過程序直接指定(指定固定的初始速度)。SpringAnimation
就是彈簧動畫,動畫元素的運動與彈簧有關FlingAnimation
經過 setStartVelocity()
方法設置初始速度,經過 setMinValue()
和 setMaxValue()
約束動畫值的範圍,經過 setFriction()
設置摩擦力(若是不設置默認爲 1)。若是動畫的屬性不是以像素爲單位的,那麼須要經過 setMinimumVisibleChange()
方法設置用戶可察覺到動畫值的最小更改,如對於 TRANSLATION_X
,TRANSLATION_Y
,TRANSLATION_Z
,SCROLL_X
,SCROLL_Y
1 像素的更改就對用戶可見了,而對於 ROTATION
,ROTATION_X
,ROTATION_Y
最小可見更改是 MIN_VISIBLE_CHANGE_ROTATION_DEGREES
即 1/10 像素,對於 ALPHA
最小可見更改是 MIN_VISIBLE_CHANGE_ALPHA
即 1/256 像素,對於 SCALE_X
和 SCALE_Y
最小可見更改是 MIN_VISIBLE_CHANGE_SCALE
即 1/500 像素,計算公式爲: 自定義屬性值的範圍 / 動畫的變化像素範圍。SpringAnimation
須要先鞏固一下彈簧的知識,彈簧有一個屬性叫阻尼比 ζ(damping ratio),是實際的粘性阻尼係數 C 與臨界阻尼係數 Cr 的比。ζ = 1 時爲臨界阻尼,這是最小的能阻止系統震盪的狀況,系統能夠最快回到平衡位置。0 < ζ < 1 時爲欠阻尼,物體會做對數衰減振動。ζ > 1 時爲過阻尼,物體會沒有振動地緩慢回到平衡位置。ζ = 0 表示不考慮阻尼,震動會一直持續下去不會中止。默認是 SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
即 0.5,能夠經過 getSpring().setDampingRatio()
設置。彈簧另外一個屬性叫剛度(stiffness),剛度越大形變產生的力就越大,默認是 SpringForce.STIFFNESS_MEDIUM
即 1500.0,能夠經過 getSpring().setStiffness()
設置
FlingAnimation
和 SpringAnimation
動畫經過 setStartVelocity()
設置固定的初始速度時最好用 dp/s 轉成 px/s : TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics())
,用戶手勢的初始速度能夠經過 GestureDetector.OnGestureListener
或 VelocityTracker
計算SpringAnimation
動畫使用 start()
方法開始動畫時屬性值不會立刻變化,而是在每次動畫脈衝即繪製以前更改。animateToFinalPosition()
方法會立刻設置最終的屬性值,若是動畫沒開始就開始動畫,這在鏈式依賴的彈簧動畫中很是有用。cancel()
方法能夠結束動畫在其當前位置,skipToEnd()
方法會跳轉至終止值再結束動畫,能夠經過 canSkipToEnd()
方法判斷是不是阻尼動畫X
,Y
,SCALE_X
,SCALE_Y
屬性便可,不過要先計算好兩個 view 最終的位置和初始縮放比android:animateLayoutChanges="true"
屬性告訴系統開啓默認動畫,或者經過 LayoutTransition
API 設置Scene
和結束 Scene
開始過渡動畫,Scene
存儲着 view hierarchy 狀態,包括全部 view 和其屬性值,開始 Scene
能夠經過 setExitAction()
定義過渡動畫開始前要執行的操做,結束 Scene
能夠經過 Scene.setEnterAction()
定義過渡動畫完成後要執行的操做。若是 view hierarchy 是靜態不變的,能夠經過佈局文件描述和加載 Scene.getSceneForLayout(mSceneRoot, R.layout.a_scene, this)
,不然能夠手動建立 new Scene(mSceneRoot, mViewHierarchy)
。Transition
的內置子類包括 AutoTransition
、Fade
、ChangeBounds
,能夠在 res/transition/
目錄下定義內置的 <fade xmlns:android="http://schemas.android.com/apk/res/android" />
,多個組合包裹在 <transitionSet>
標籤中,而後使用 TransitionInflater.from(this).inflateTransition(R.transition.fade_transition)
加載。還能夠手動建立 new Fade()
。開始過渡動畫時只須要執行 TransitionManager.go(mEndingScene, mFadeTransition)
便可。默認是對 Scene
中全部的 view 做動畫,能夠經過 addTarget()
或 removeTarget()
在開始過渡動畫前進行調整。若是不想在兩個 view hierarchy 間進行過渡,而是在同一個 view hierarchy 狀態更改後執行過渡動畫,那就不須要使用 Scene
了,先利用 TransitionManager.beginDelayedTransition(mRootView, mFade)
讓系統記錄 view 的更改,而後增刪 view 來更改 view hierarchy 的狀態,系統會在重繪 UI 時執行延遲過渡動畫。因爲 SurfaceView
由非 UI 線程更新,因此它的過渡可能有問題,TextureView
在一些過渡類型上可能有問題,AdapterView
與過渡動畫框架不兼容,TextView
的大小過渡動畫可能有問題android:windowActivityTransitions
屬性設置爲 true
或者代碼中手動 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)
開啓。setExitTransition()
和 setSharedElementExitTransition()
方法能夠爲起始 Activity 設置退出過渡動畫,setEnterTransition()
和 setSharedElementEnterTransition()
方法能夠爲目標 Activity 設置進入過渡動畫。激活目標 Activity 的時候須要攜帶 ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
的 Bundle,返回的時候要使用 finishAfterTransition()
方法。共享元素須要使用 android:transitionName
屬性或者 View.setTransitionName()
方法指定名字,多個共享元素使用 Pair.create(view1, "agreedName1")
傳遞信息Transition
,實現 captureStartValues()
和 captureEndValues()
方法捕獲過渡的 view 屬性值並告訴過渡框架,具體實現爲經過 transitionValues.view
檢索當前 view,經過 transitionValues.values.put(PROPNAME_BACKGROUND, view.getBackground())
存儲屬性值,爲了不衝突 key 的格式必須爲 package_name:transition_name:property_name
。同時還要實現 createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)
方法,框架調用這個方法的次數取決於開始和結束 scene 須要更改的元素數TextView
寬和高都不能是 wrap_content
,autoSizeTextType
默認是 none
,設置爲 uniform
開啓自適應,默認最小 12sp
,最大 112sp
,粒度 1px
。autoSizePresetSizes
屬性能夠設置預置的一些大小res/font/
,使用屬性爲 fontFamily
,獲取 Typeface
爲 getResources().getFont(R.font.myfont);
,兼容庫使用 ResourcesCompat.getFont(context, R.font.myfont)
Magnifier
的 show()
方法的參數是相對於被放大 View 的左上角的座標<com.myapp.MyDrawable>
,公共靜態內部類可使用 class 屬性 class="com.myapp.MyTopLevelClass$MyDrawable"
vectorDrawables.useSupportLibrary = true
並使用 VectorDrawableCompat
和 AnimatedVectorDrawableCompat
NETWORK_STATE_CHANGED_ACTION
廣播再也不包含 SSID,BSSID 等信息BroadcastReceiver
,免除這項限制的廣播包括 ACTION_LOCKED_BOOT_COMPLETED
等不太可能影響用戶體驗的廣播ACTION_NEW_PICTURE
和 ACTION_NEW_VIDEO
系統廣播,能夠經過 JobInfo
和 JobParameters
完成。不能靜態註冊 CONNECTIVITY_ACTION
廣播,若是想在網絡變化時調度任務能夠選擇使用 WorkManager,若是隻在應用運行期間監聽網絡變化使用 ConnectivityManager
比動態註冊註銷 BroadcastReceiver
更優雅BroadcastReceiver
onReceive()
方法中不能進行復雜工做不然會致使 ANR,onReceive()
方法一旦執行完,系統可能就認爲這個廣播接收器已經沒用了,隨時會殺掉包含這個廣播接收器的進程,包括這個進程啓動的線程。使用 goAsync()
方法能夠在 PendingResult#finish()
方法執行前爲廣播接收器的存活爭取更多的時間,但最好仍是使用 JobScheduler
等方式進行長時間處理工做sendBroadcast()
方法發的廣播屬於常規廣播,全部能接收這個廣播的廣播接收器接收到廣播的順序是不可控的sendOrderedBroadcast()
方法發的廣播屬於有序廣播,根據廣播接收器的優先級一個接一個地傳遞這條廣播,相同優先級的順序不可控,廣播接收器能夠選擇繼續傳遞給下一個,也能夠選擇直接丟掉LocalBroadcastManager.getInstance(this).sendBroadcast()
方法發的廣播屬於應用進程內的本地廣播,這樣的廣播只有應用本身知道,比系統級的全局廣播更安全更有效率/data/data/your.application.package/
或 /data/user/0/your.application.package/
,當卸載應用時這個目錄也會被刪除。這個目錄除了系統和應用本身誰都沒法訪問,除非擁有權限。這個路徑下有個 files/
子目錄用來存儲應用的文件,能夠經過 getFilesDir()
方法獲取這個路徑表示,能夠經過 openFileOutput(filename, Context.MODE_PRIVATE)
寫這個目錄下的文件。還有一個 cache/
子目錄用來存儲臨時緩存文件,系統可能會在存儲空間不足時清理這個目錄,能夠經過 getCacheDir()
方法獲取這個路徑表示,能夠經過 File#createTempFile(fileName, null, context.getCacheDir())
方法在這個目錄下建立一個臨時文件。還有一個 shared_prefs/
子目錄用來以 XML 文件的形式存儲簡單的鍵值對數據,須要使用 SharedPreferences
API 進行管理READ_EXTERNAL_STORAGE
或 WRITE_EXTERNAL_STORAGE
權限,而後檢查外存是否可用: Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
表示可寫,Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)
表示可讀。外存根目錄可使用 Environment.getExternalStorageDirectory()
方法獲取,通常是 /storage/emulated/0/
,使用 new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName)
能夠讀寫外存公有目錄的文件。使用 getExternalFilesDir(null)
能夠獲取該應用的外存根目錄,通常是 /storage/emulated/0/Android/data/your.application.package/files
,使用 new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName)
能夠讀寫文件,應用的外存目錄也會在卸載應用時被刪除。使用 getExternalCacheDir()
能夠獲取應用的外存緩存目錄。myFile.delete()
或 myContext.deleteFile(fileName)
刪除文件SharedPreferences
的方式有三個: 經過 PreferenceManager.getDefaultSharedPreferences()
能夠獲取或建立名字爲 context.getPackageName() + "_preferences"
模式爲 Context.MODE_PRIVATE
的文件。經過 MainActivity.this.getPreferences(Context.MODE_PRIVATE)
能夠獲取或建立名字爲當前 Activity
類名的文件。使用 context.getSharedPreferences("file1", Context.MODE_PRIVATE)
能夠獲取或建立名字是 file1 的文件。MODE_WORLD_READABLE
和 MODE_WORLD_WRITEABLE
從 Android 7.0 (API level 24) 開始被禁止使用了。commit()
方法會將數據同步寫到磁盤因此可能會阻塞 UI,而 apply()
方法會異步寫到磁盤。FileProvider
做爲 ContentProvider
的特殊子類,它的 getUriForFile()
靜態方法能夠爲文件生成 content URI。<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapp.fileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
複製代碼
<paths>
<files-path path="images/" name="myimages" />
</paths>
複製代碼
android:authorities
屬性通常是以當前應用包名爲前綴的字符串,用來標誌數據的全部者,多個的話用分號隔開<files-path/>
表明 getFilesDir()
<cache-path/>
表明 getCacheDir()
<external-path/>
表明 Environment.getExternalStorageDirectory()
<external-files-path>
表明 getExternalFilesDir(null)
<external-cache-path>
表明 getExternalCacheDir()
<external-media-path>
表明 getExternalMediaDirs()
File imagePath = new File(getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.example.myapp.fileprovider", newFile);
複製代碼
FLAG_GRANT_READ_URI_PERMISSION
或 FLAG_GRANT_WRITE_URI_PERMISSION
的 flag 授予對這個 content URI 的臨時訪問權限,該權限會被目標 Activity
所在應用的其它組件繼承,會在所在的任務結束時自動撤銷受權Context.grantUriPermission(package, Uri, mode_flags)
方法也能夠授予 FLAG_GRANT_READ_URI_PERMISSION
或 FLAG_GRANT_WRITE_URI_PERMISSION
權限,但只有在主動調用 revokeUriPermission()
方法後或者重啓系統後纔會撤銷受權List<ResolveInfo> activities = getPackageManager().queryIntentActivities(intent,
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
的數據形式和關係型數據庫的表格數據相似,所以 API 也像數據庫同樣包含增刪改查(CRUD)操做,但爲了更好地組織管理一個或多個 ContentProvider
,最好經過 ContentResolver
操做 ContentProvider
ContentProvider
的增刪改查操做,不能直接在 UI 線程上執行Uri
和 ContentUris
類的靜態方法能夠方便地構造 content URISELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
複製代碼
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI,
mProjection,
mSelectionClause,
mSelectionArgs,
mSortOrder);
複製代碼
ContentProvider
所在應用自己的組件能夠隨便訪問它,不須要受權ContentProvider
的應用不指定任何權限,那麼其它應用就沒法訪問這個 ContentProvider
的數據<uses-permission>
標籤獲取訪問權限ContentProvider
須要繼承 ContentProvider
並實現增刪改查等一系列方法: onCreate()
在系統建立 provider 後立刻調用,能夠在這裏建立數據庫,但不要在這裏作耗時操做。getType()
返回 content URI 的 MIME 類型。query()
、insert()
、update()
、delete()
進行增刪改查。除了 onCreate()
方法其它方法必需要保證是線程安全的REQUEST_INSTALL_PACKAGES
權限,數據必須經過 FileProvider
形式共享,數據類型是 application/vnd.android.package-archive
,必須給 Intent 添加 FLAG_GRANT_READ_URI_PERMISSION
flag 授予臨時訪問權限Intent installIntent = new Intent(Intent.ACTION_VIEW);
File apkPath = new File(Environment.getExternalStorageDirectory(), "apks");
File apkFile = new File(apkPath, "myapp.apk");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.example.myapp.fileprovider", apkFile);
installIntent.setDataAndType(contentUri, "application/vnd.android.package-archive");
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
installIntent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (installIntent.resolveActivity(getPackageManager()) != null) {
startActivity(installIntent);
}
複製代碼
IMPORTANCE_HIGH
對應 PRIORITY_HIGH
或 PRIORITY_MAX
,IMPORTANCE_DEFAULT
對應 PRIORITY_DEFAULT
,IMPORTANCE_LOW
對應 PRIORITY_LOW
,IMPORTANCE_MIN
對應 PRIORITY_MIN
。在應用啓動時能夠執行下面的代碼建立通知類別,能夠無反作用地屢次執行private void createNotificationChannel() {
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);
}
}
複製代碼
NotificationChannel
的 enableLights()
,setLightColor()
等方法能夠指定該通知類別默認的通知行爲,可是一旦建立了應用就不能再對它作任何更改了,只有用戶本身能夠更改設置。能夠經過 Intent 引導用戶跳轉至對應設置頁Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
startActivity(intent);
複製代碼
getNotificationChannel()
、getNotificationChannels()
、getVibrationPattern()
、getImportance()
等方法獲取deleteNotificationChannel(id)
能夠刪除通知類別,可是在開發模式下可能須要重裝應用或者清除數據纔會徹底刪除// The id of the group.
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));
複製代碼
setCategory()
方法能夠設置所屬的系統範圍的勿擾類別NotificationCompat
和 NotificationManagerCompat
等兼容庫中的類以便方便地適配低版本系統setSmallIcon()
方法能夠設置小圖標,應用名和時間是由系統設置的,setLargeIcon()
方法能夠設置右邊大圖標,setContentTitle()
和 setContentText()
方法能夠設置通知的標題和內容,setPriority()
方法能夠爲 Android 8.0 (API level 26) 如下的系統設置通知優先級。系統範圍的預約義通知類別包括 NotificationCompat.CATEGORY_ALARM
,NotificationCompat.CATEGORY_REMINDER
等類別,這個類別在勿擾模式中有用,能夠經過 setCategory()
方法指定所屬的系統範圍通知類別setStyle()
方法設置其餘可展開的通知樣式,.setStyle(new NotificationCompat.BigTextStyle().bigText(emailObject.getSubjectAndSnippet()))
能夠設置大文本塊樣式。.setStyle(new NotificationCompat.InboxStyle().addLine(messageSnippet1)
能夠設置多行的 inbox 樣式。.setStyle(new NotificationCompat.MessagingStyle(resources.getString(R.string.reply_name)).addMessage(message1))
能夠設置消息樣式,可是此樣式會忽略 setContentTitle()
和 setContentText()
方法的設置,但能夠經過 setConversationTitle()
方法設置該聊天所屬的羣組名。setStyle(new android.support.v4.media.app.Notification.MediaStyle().setShowActionsInCompactView(1).setMediaSession(mMediaSession.getSessionToken()))
能夠設置媒體樣式的通知,屬於 CATEGORY_TRANSPORT
類別。setContentIntent()
方法設置 PendingIntent
對象完成,setAutoCancel(true)
能夠在點擊後自動移除通知Intent intent = new Intent(this, AlertDetails.class);
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);
複製代碼
NotificationManagerCompat#notify()
方法能夠顯示通知,你須要定義一個惟一的 int 值的 ID 做爲這個通知的 ID,保存這個 ID 以便以後更新或移除這個通知NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, mBuilder.build());
複製代碼
addAction()
方法能夠添加操做按鈕private static final String KEY_TEXT_REPLY = "key_text_reply";
String replyLabel = getResources().getString(R.string.reply_label);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel(replyLabel)
.build();
PendingIntent replyPendingIntent =
PendingIntent.getBroadcast(getApplicationContext(),
conversation.getConversationId(),
getMessageReplyIntent(conversation.getConversationId()),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon,
getString(R.string.label), replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
Notification newMessageNotification = new Notification.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle(getString(R.string.title))
.setContentText(getString(R.string.content))
.addAction(action)
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, newMessageNotification);
複製代碼
private CharSequence getMessageText(Intent intent) {
// 在 BroadcastReceiver 接收的 Intent 中能夠根據以前的 KEY 拿到文本框的內容
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
return null;
}
複製代碼
// 在回覆完成後更新通知表示已經處理此次回覆,也能夠調用 setRemoteInputHistory() 方法附加回復的內容
Notification repliedNotification = new Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentText(getString(R.string.replied))
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, repliedNotification);
複製代碼
setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false)
能夠給通知添加肯定進度條,經過 setProgress(0, 0, true)
能夠添加不肯定進度條,經過 setProgress(0, 0, false)
能夠在完成後移除進度條setVisibility()
方法能夠設置鎖屏時的通知顯示策略,VISIBILITY_PUBLIC
(顯示全部通知)表示完整地顯示通知內容,VISIBILITY_SECRET
(徹底不顯示內容)表示不顯示通知的任何信息,VISIBILITY_PRIVATE
(隱藏敏感通知內容)表示只顯示圖標標題等基本信息NotificationManagerCompat#notify()
方法傳遞以前的通知 ID 能夠更新以前的通知,調用 setOnlyAlertOnce()
方法以便只在第一次出現通知時提示用戶setAutoCancel()
方法能夠在用戶點擊通知後清除通知,建立通知時調用 setTimeoutAfter()
方法能夠在超時後由系統自動清除通知,能夠隨時調用 cancel()
或 cancelAll()
方法清除以前的通知Activity
分爲兩種,一種是應用的正經常使用戶體驗流中的常規 Activity
,擁有任務完整的返回棧。還有一種是僅僅用來展現通知的詳細內容的特殊Activity
,它不須要返回棧。Activity
須要先經過 android:parentActivityName
屬性或者 android.support.PARENT_ACTIVITY
的 <meta-data>
標籤指定層級關係,而後Intent resultIntent = new Intent(this, ResultActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
複製代碼
Activity
須要先指定 android:taskAffinity=""
和 android:excludeFromRecents="true"
以免在以前的任務中啓動,而後Intent notifyIntent = new Intent(this, ResultActivity.class);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(
this, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT
);
複製代碼
setGroup()
方法指定所屬分組,經過 setSortKey()
方法排序,經過 setGroupAlertBehavior()
指定通知行爲,默認是 GROUP_ALERT_ALL
表示組內全部的通知均可能產生聲音和震動。系統默認會自動生成通知組的摘要,你也能夠單首創建一個表示通知組摘要的通知int SUMMARY_ID = 0;
String GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL";
Notification newMessageNotification1 =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notify_email_status)
.setContentTitle(emailObject1.getSummary())
.setContentText("You will not believe...")
.setGroup(GROUP_KEY_WORK_EMAIL)
.build();
Notification newMessageNotification2 =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notify_email_status)
.setContentTitle(emailObject2.getSummary())
.setContentText("Please join us to celebrate the...")
.setGroup(GROUP_KEY_WORK_EMAIL)
.build();
Notification summaryNotification =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setContentTitle(emailObject.getSummary())
//set content text to support devices running API level < 24
.setContentText("Two new messages")
.setSmallIcon(R.drawable.ic_notify_summary_status)
//build summary info into InboxStyle template
.setStyle(new NotificationCompat.InboxStyle()
.addLine("Alex Faarborg Check this out")
.addLine("Jeff Chang Launch Party")
.setBigContentTitle("2 new messages")
.setSummaryText("janedoe@example.com"))
//specify which group this notification belongs to
.setGroup(GROUP_KEY_WORK_EMAIL)
//set this notification as the summary for the group
.setGroupSummary(true)
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(emailNotificationId1, newMessageNotification1);
notificationManager.notify(emailNotificationId2, newMessageNotification2);
notificationManager.notify(SUMMARY_ID, summaryNotification);
複製代碼
mChannel.setShowBadge(false)
能夠禁用小圓點標誌,調用 setNumber(messageCount)
能夠設置長按後顯示給用戶的消息數,調用 setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
能夠設置長按後的圖標樣式,經過 setShortcutId()
能夠隱藏重複的 shortcutsetStyle(new NotificationCompat.DecoratedCustomViewStyle())
樣式,而後調用 setCustomContentView()
和 setCustomBigContentView()
方法指定自定義的摺疊和展開佈局(通常摺疊佈局限制高度爲 64 dp,展開佈局高度限制爲 256 dp),佈局中的控件要使用兼容庫的樣式,如 style="@style/TextAppearance.Compat.Notification.Title"
。若是不想使用標準通知模板,不調用 setStyle()
只調用 setCustomBigContentView()
便可Activity
正處於全屏模式並使用了 fullScreenIntent
;Android 8.0 (API level 26) 及更高的設備上通知的重要程度爲 IMPORTANCE_HIGH
;Android 8.0 (API level 26) 如下的設備上通知的優先級爲 PRIORITY_HIGH
或 PRIORITY_MAX
而且開啓了鈴聲或震動Service
運行在所在進程的主線程中,它不會自動建立線程,若是你不指定進程的話它甚至不會建立額外的進程,因此若是要執行耗時操做的話應該建立一個新的線程去執行Service
有三種類型,一種是 Foreground service,用來執行用戶能從通知欄感知到的後臺操做,一種是 Background service,是用戶感知不到的,可是 Android 8.0 (API level 26) 開始系統會對這類 Service
進行各類限制,一種是 Bound service,是經過 bindService()
方法綁定到其餘組件的服務,Bound service 基於 C/S 架構的思想,被綁定的組件能夠向這個服務發請求、接收響應數據、甚至 IPC 交互,只要有一個組件綁定了它,他就會立刻運行,多個組件能夠同時綁定它,當全部都解綁時,它就會被銷燬startService()
方法啓動 Service
時會致使它收到 onStartCommand()
回調,服務會一直運行直到你主動調用它的 stopSelf()
方法或其它組件主動調用 stopService()
方法。若是它只是 Bound service 能夠不是實現該方法bindService()
方法啓動 Service
時不會回調 onStartCommand()
方法,而會回調 onBind()
方法,爲了與它的 client 通訊這個方法須要返回 IBinder
,若是你不容許它綁定就返回空,解綁使用 unbindService()
方法Service
能夠啓動屢次,可是隻能中止一次,stopSelf(int)
方法能夠在知足指定的 startId
時中止Service
和 Activity
同樣其所在的的進程可能隨時被系統殺掉,一樣須要作好銷燬重建的工做IntentService
做爲 Service
的子類能夠方便地在工做線程中完成多個任務,多個任務是一個接一個的執行,因此不會存在線程安全問題,內部是藉助一個 HandlerThread
實現異步處理的,當全部請求都完成後會自動銷燬,onBind()
方法返回了空,爲了方便調試能夠在構造器中指定工做線程的名字,若是想重寫 onCreate()
、onStartCommand()
、onDestroy()
方法的實現必須調用父類的實現,IntentService
是在一個工做線程中完成多個任務,因此若是想在多個線程中完成多個任務能夠直接繼承 Service
並藉助 HandlerThread
等線程技術實現IntentService
更推薦使用 JobIntentService
,JobIntentService
藉助了 JobScheduler
和 AsyncTask
完成更靈活的任務調度和處理,只須要申請好 WAKE_LOCK
權限 JobScheduler
就能夠完成 WakeLock
的管理,使用 enqueueWork(Context, Class, int, Intent)
靜態方法提交任務就可讓 onHandleWork(Intent)
回調中的代碼被更好地調度執行了Service
只能經過 PendingIntent
進行組件間的通訊FOREGROUND_SERVICE
權限startForeground()
方法能夠向系統請求以 Foreground service 模式運行,stopForeground()
能夠請求退出該模式JobScheduler
實現,在以前的系統上藉助 BroadcastReceiver
和 AlarmManager
實現OneTimeWorkRequest
,週期性的任務使用 PeriodicTimeWorkRequest
Constraints constraints = new Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.build();
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setConstraints(constraints)
.build();
複製代碼
setInitialDelay(10, TimeUnit.MINUTES)
設置一個最小延時Worker
中使用 Result.retry()
完成,採用的補償策略默認是 EXPONENTIAL
指數級的,可使用 setBackoffCriteria()
方法調整策略setInputData()
方法爲任務設置輸入數據,在 Worker
中能夠經過 getInputData()
方法獲取到輸入數據,Result.success()
和 Result.failure()
能夠攜帶輸出數據。數據應該儘量的簡單,不能超過 10KBaddTag
方法能夠給任務打 Tag,而後就可使用 WorkManager.cancelAllWorkByTag(String)
和 WorkManager.getWorkInfosByTagLiveData(String)
等方法方便操做任務了BLOCKED
態ENQUEUED
態RUNNING
態Result.success()
那麼會被認爲是 SUCCEEDED
態,這是最終態,只有 OneTimeWorkRequest
可能進入這個狀態Result.failure()
那麼會被認爲是 FAILED
態,這是最終態,只有 OneTimeWorkRequest
可能進入這個狀態,全部相關的任務也會被標記爲 FAILED
且不會被執行WorkRequest
會被認爲是 CANCELLED
態,全部相關的任務也會被標記爲 CANCELLED
且不會被執行WorkManager.getWorkInfoById(UUID)
和 WorkManager.getWorkInfoByIdLiveData(UUID)
等方法能夠定位想要的任務進行觀察WorkManager.getInstance()
.beginWith(Arrays.asList(filter1, filter2, filter3))
.then(compress)
.then(upload)
.enqueue();
複製代碼
對於用戶觸發的必須立刻執行且必須執行完的後臺任務,須要使用 Foreground services 實現,它既告訴系統這個應用正在執行重要的任務不能被殺掉,又經過通知欄告訴用戶有後臺工做正在執行php
若是任務須要在精確的時間點執行,可使用 AlarmManager
java
若是須要執行一個長時間的 HTTP 下載任務,可使用 DownloadManager
。DownloadManager
獨立於應用以外,能夠在下載失敗、更改網絡鏈接、系統重啓後進行重試android
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);
}
複製代碼
adb shell am start
-W -a android.intent.action.VIEW
-d "example://gizmos" com.example.android
複製代碼
adb shell am start -a android.intent.action.VIEW
-c android.intent.category.BROWSABLE
-d "http://domain.name:optional_port"
複製代碼
adb shell dumpsys package domain-preferred-apps
複製代碼
adb shell am kill com.some.package
複製代碼
adb push com.some.package /sdcard/
複製代碼
.nomedia
文件會致使其所在目錄不被 Media Scanner 掃描到/** * 華爲手機劉海屏適配 * * @author frank * @see <a href="https://developer.huawei.com/consumer/cn/devservice/doc/50114">《華爲劉海屏手機安卓O版本適配指導》</a> */
public class HwNotchSizeUtil {
private static final int FLAG_NOTCH_SUPPORT = 0x00010000;
/** * 是不是劉海屏手機 * * @param context Context * @return true:劉海屏 false:非劉海屏 */
public static boolean hasNotchInScreen(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/** * 獲取劉海尺寸 * * @param context Context * @return int[0]值爲劉海寬度 int[1]值爲劉海高度 */
public static int[] getNotchSize(Context context) {
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/** * 設置應用窗口在華爲劉海屏手機使用劉海區 * * @param window Window */
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
WindowManager.LayoutParams layoutParams = window.getAttributes();
try {
Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
Constructor con = layoutParamsExCls.getConstructor(ViewGroup.LayoutParams.class);
Object layoutParamsExObj = con.newInstance(layoutParams);
Method method = layoutParamsExCls.getMethod("addHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
e.printStackTrace();
}
}
/** * 設置應用窗口在華爲劉海屏手機不使用劉海區顯示 * * @param window Window */
public static void setNotFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
WindowManager.LayoutParams layoutParams = window.getAttributes();
try {
Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx");
Constructor con = layoutParamsExCls.getConstructor(ViewGroup.LayoutParams.class);
Object layoutParamsExObj = con.newInstance(layoutParams);
Method method = layoutParamsExCls.getMethod("clearHwFlags", int.class);
method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
/** * 小米手機劉海屏適配 * * @author frank * @see <a href="https://dev.mi.com/console/doc/detail?pId=1293">《小米劉海屏 Android O 適配》</a> * @see <a href="https://dev.mi.com/console/doc/detail?pId=1341">《小米劉海屏 Android P 適配》</a> */
public class XiaomiNotchSizeUtil {
private static final int FLAG_NOTCH_OPEN = 0x00000100;
private static final int FLAG_NOTCH_PORTRAIT = 0x00000200;
private static final int FLAG_NOTCH_LANDSCAPE = 0x00000400;
/** * 是不是劉海屏手機 * * @param context Context * @return true:劉海屏 false:非劉海屏 */
public static boolean hasNotchInScreen(Context context) {
boolean ret = false;
try {
ret = "1".equals(getSystemProperty("ro.miui.notch"));
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/** * 獲取劉海尺寸 * * @param context Context * @return int[0]值爲劉海寬度 int[1]值爲劉海高度 */
public static int[] getNotchSize(Context context) {
int[] ret = new int[]{0, 0};
try {
int widthResId = context.getResources().getIdentifier("notch_width", "dimen", "android");
if (widthResId > 0) {
ret[0] = context.getResources().getDimensionPixelSize(widthResId);
}
int heightResId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (heightResId > 0) {
ret[1] = context.getResources().getDimensionPixelSize(heightResId);
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/** * 橫豎屏都繪製到耳朵區 * * @param window Window */
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
try {
Method method = Window.class.getMethod("addExtraFlags",
int.class);
method.invoke(window, FLAG_NOTCH_OPEN | FLAG_NOTCH_PORTRAIT | FLAG_NOTCH_LANDSCAPE);
} catch (Exception e) {
e.printStackTrace();
}
}
/** * 橫豎屏都不會繪製到耳朵區 * * @param window Window */
public static void setNotFullScreenWindowLayoutInDisplayCutout(Window window) {
if (window == null) {
return;
}
try {
Method method = Window.class.getMethod("clearExtraFlags",
int.class);
method.invoke(window, FLAG_NOTCH_OPEN | FLAG_NOTCH_PORTRAIT | FLAG_NOTCH_LANDSCAPE);
} catch (Exception e) {
e.printStackTrace();
}
}
private static String getSystemProperty(String key) {
String ret = null;
BufferedReader bufferedReader = null;
try {
Process process = Runtime.getRuntime().exec("getprop " + key);
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuilder stringBuilder = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
ret = stringBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return ret;
}
}
複製代碼
/** * OPPO手機劉海屏適配 * * @author frank * @see <a href="https://open.oppomobile.com/wiki/doc#id=10159">《OPPO凹形屏適配說明》</a> */
public class OppoNotchSizeUtil {
/** * 是不是劉海屏手機 * * @param context Context * @return true:劉海屏 false:非劉海屏 */
public static boolean hasNotchInScreen(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
}
複製代碼
/** * VIVO手機劉海屏適配 * * @author frank * @see <a href="https://dev.vivo.com.cn/documentCenter/doc/103">《異形屏應用適配指南》</a> */
public class VivoNotchSizeUtil {
private static final int MASK_NOTCH_IN_SCREEN = 0x00000020;
private static final int MASK_ROUNDED_IN_SCREEN = 0x00000008;
/** * 是不是劉海屏手機 * * @param context Context * @return true:劉海屏 false:非劉海屏 */
public static boolean hasNotchInScreen(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class FtFeature = cl.loadClass("android.util.FtFeature");
Method get = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) get.invoke(FtFeature, MASK_NOTCH_IN_SCREEN);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
}
複製代碼
/** * 錘子手機劉海屏適配 * * @author frank * @see <a href="https://resource.smartisan.com/resource/61263ed9599961d1191cc4381943b47a.pdf">《Smartisan 開發者文檔》</a> */
public class SmartisanNotchSizeUtil {
private static final int MASK_NOTCH_IN_SCREEN = 0x00000001;
/** * 是不是劉海屏手機 * * @param context Context * @return true:異形屏 false:非異形屏 */
public static boolean hasNotchInScreen(Context context) {
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class DisplayUtilsSmt = cl.loadClass("smartisanos.api.DisplayUtilsSmt");
Method get = DisplayUtilsSmt.getMethod("isFeatureSupport", int.class);
ret = (boolean) get.invoke(DisplayUtilsSmt, MASK_NOTCH_IN_SCREEN);
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
}
複製代碼
findViewById(R.id.chooseImg).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_IMAGE_GET);
}
}
});
findViewById(R.id.takePicture).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
ex.printStackTrace();
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,
"com.your.package.fileprovider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}
});
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_GET && resultCode == RESULT_OK) {
Uri fullPhotoUri = data.getData();
ParcelFileDescriptor descriptor;
try {
descriptor = getContentResolver().openFileDescriptor(fullPhotoUri, "r");
FileDescriptor fd = descriptor.getFileDescriptor();
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
imageView.setImageBitmap(bitmap);
processImage();
} catch (Exception e) {
e.printStackTrace();
}
} else if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
int targetW = imageView.getWidth();
int targetH = imageView.getHeight();
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
int scaleFactor = Math.min(photoW / targetW, photoH / targetH);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
imageView.setImageBitmap(bitmap);
processImage();
}
}
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(imageFileName, ".jpg", storageDir);
currentPhotoPath = image.getAbsolutePath();
return image;
}
複製代碼
<paths>
<external-path name="my_images" path="Android/data/com.your.package/files/Pictures" />
</paths>
複製代碼