網(wǎng)站首頁 編程語言 正文
Android 6
本文根據(jù)我個人的開發(fā)經(jīng)驗,總結(jié)了從 Android 6 - Android 13 重要的行為變更。當然,這不是 Android 所有的行為變更,這里只是列舉了我覺得比較有影響的,比較常見的一些場景的開發(fā)適配。那么,下面讓我們一起來瞧瞧,有哪些行為變更是需要我們特別注意的。
在 Android 6 版本開始引進運行時權(quán)限機制,Android 將所有的權(quán)限歸為兩類,一類是普通權(quán)限,一類是危險權(quán)限。普通權(quán)限一般不會威脅到用戶的安全和隱私,對于這部分權(quán)限,系統(tǒng)自動對軟件進行授權(quán),不需要詢問用戶。而危險權(quán)限是可能對用戶的安全和隱私造成影響的權(quán)限,如獲取設(shè)備地理位置、獲取設(shè)備聯(lián)系人信息等,這些就需要明確通知用戶,并由用戶手動進行授權(quán)才可以進行相應(yīng)操作。
危險權(quán)限如下所示:
權(quán)限組名 | 權(quán)限名 |
---|---|
CALENDAR(日歷) | READ_CALENDAR,WRITE_CALENDAR |
CAMERA(攝像頭) | CAMERA |
CONTACTS(聯(lián)系人) | READ_CONTACTS,WRITE_CONTACTS,GET_ACCOUNTS |
LOCATION(定位) | ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION |
MICROPHONE(麥克風(fēng)) | RECORD_AUDIO |
PHONE(手機) | READ_PHONE_STATE,CALL_PHONE,READ_CALL_LOG,WRITE_CALL_LOG,ADD_VOICEMAIL,USE_SIP,PROCESS_OUTGOING_CALLS |
SENSOR(傳感器) | BODY_SENSORS |
SMS(短信) | SEND_SMS,RECEIVE_SMS,READ_SMS,RECEIVE_WAP_PUSH,RECEIVE_MMS |
STORAGE(存儲) | READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE |
運行時權(quán)限動態(tài)申請,這里推薦郭神的開源庫 - PermissionX,使用簡單方便,這里不再贅述。
Android 7
Android 7 禁止向你的應(yīng)用外公開 file://URI, 如果在 Android 7 及以上系統(tǒng)傳遞 file:// URI 就會觸發(fā) FileUriExposedException,不適配的話在 Android 7 及以上系統(tǒng)就會出現(xiàn)應(yīng)用崩潰的現(xiàn)象。如果要在應(yīng)用間共享文件,可以發(fā)送 content://URI 類型的 URI,并授予 URI 臨時訪問權(quán)限,這就需要用到 FileProvider 類。
我們以調(diào)用系統(tǒng)相機拍照為例,在 res 下創(chuàng)建 xml 目錄,在此目錄下創(chuàng)建 file_paths.xml 文件。
<?xml version="1.0" encoding="utf-8"?> <paths> <!-- 內(nèi)部存儲,對應(yīng) filesDir,路徑:/data/data/package_name/files--> <files-path name="files_path" path="." /> <!-- 內(nèi)部存儲,對應(yīng) cacheDir,路徑:/data/data/package_name/cache--> <cache-path name="cache_path" path="." /> <!--外部存儲,對應(yīng) getExternalFilesDir,路徑:/storage/sdcard/Android/data/package_name/files--> <external-files-path name="external_files_path" path="." /> <!--外部存儲,對應(yīng) externalCacheDir,路徑:/storage/sdcard/Android/data/package_name/cache--> <external-cache-path name="external_cache_path" path="." /> </paths>
在 AndroidManifest 中注冊 FileProvider
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.myapplication.fileProvider" android:exported="false" android:grantUriPermissions="true"> <!--exported 要為 false,否則會報安全異常,grantUriPermissions 為 true,表示授予 URI 臨時訪問權(quán)限--> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
適配
val pictureFile =
File(getExternalFilesDir(null), "${System.currentTimeMillis()}.jpg")
if (!pictureFile.exists()) {
pictureFile.createNewFile()
}
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 通過 FileProvider 創(chuàng)建一個 content 類型的 Uri
val pictureUri =
FileProvider.getUriForFile(
this,
"com.example.myapplication.fileProvider",
pictureFile
)
intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri)
// 授予目錄臨時共享權(quán)限
intent.flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
startActivity(intent)
} else {
val pictureUri = Uri.fromFile(pictureFile)
intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri)
startActivity(intent)
}
Android 8
從 Android 8 開始,Google 規(guī)定所有的通知必須分配一個渠道,每一個渠道,你都可以設(shè)置渠道中所有通知的行為。用戶界面將通知渠道稱之為通知類別,用戶可以隨意修改這些設(shè)置來決定通知的行為。
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
"Channel_ID",
"Channel_Name",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(notificationChannel)
val notification =
NotificationCompat.Builder(this, "Channel_ID")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("title").setContentText("content").build()
notificationManager.notify(1, notification)
} else {
val notification =
NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("title").setContentText("content").build()
notificationManager.notify(1, notification)
}
從 Android 8 開始,不允許后臺應(yīng)用啟動后臺服務(wù),需要使用 startForegroundService 指定為前臺服務(wù),否則系統(tǒng)會停止 Service 并拋出異常。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
val intent = Intent(this, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
class MyService : Service() {
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
"Channel_ID", "Channel_Name", NotificationManager.IMPORTANCE_DEFAULT
)
manager.createNotificationChannel(channel)
val notification = Notification.Builder(this, "Channel_ID").build()
startForeground(1, notification)
}
}
override fun onDestroy() {
super.onDestroy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true)
}
}
}
Android 9
從 Android 9 開始,限制了 HTTP 網(wǎng)絡(luò)請求,如果繼續(xù)使用 HTTP 請求,會在日志做出警告,不過只是無法正常發(fā)出請求,不會導(dǎo)致應(yīng)用崩潰。如果我們需要使用 HTTP 請求的話,需要在 AndroidManifest 中添加如下配置:
<application ... android:usesCleartextTraffic="true"> ... </application>
除了這個方法,我們也可以指定域名。在 res 的 xml 目錄下新建文件 network_config.xml
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">www.wanandroid.com</domain> </domain-config> </network-security-config>
然后在 AndroidManifest 中配置即可
<application ... android:networkSecurityConfig="@xml/network_config"> ... </application>
Android 10
在 Android 10 之前的版本,我們在做文件的操作時都會申請存儲空間的讀寫權(quán)限,但是這些權(quán)限可能被濫用,造成手機的存儲空間中充斥著大量不明作用的文件,并且應(yīng)用卸載后也沒刪除掉。為了解決這個問題,Android 10 開始引入了分區(qū)存儲的概念。
分區(qū)存儲就是對外部存儲進行了重新設(shè)計,簡單來說,對于外部共享文件,需要通過 MediaStrore API 和 Storage Access Framework 來訪問,對于外部私有文件,無法讀寫自己應(yīng)用以外創(chuàng)建的其他文件。
Android 中存儲可以分為兩大類:專屬存儲和共享存儲。
- 專屬存儲:每個應(yīng)用在都擁有自己的專屬目錄,其它應(yīng)用看不到。它包括 APP 自身的內(nèi)部存儲和外部存儲,這倆無需存儲權(quán)限便可訪問。
- 共享存儲:共享存儲空間存放的是圖片,視頻和音頻等文件,這些資源是公共的,所有 App 都能訪問它們。
舉個例子,如果想拿到共享存儲里的圖片路徑,該怎么做呢?
首先需要申請權(quán)限,這里直接使用 PermissionX 。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
PermissionX.init(this)
.permissions(Manifest.permission.READ_EXTERNAL_STORAGE).request { allGranted, _, _ ->
if (allGranted) {
Log.i(tag, "All permissions have been agreed")
} else {
Toast.makeText(this, "Please agree to the permission", Toast.LENGTH_SHORT)
.show()
}
}
通過 MediaStrore 查詢
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val cursor = contentResolver.query(uri, null, null, null, null)
cursor?.let {
val indexPhotoPath = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
while (it.moveToNext()) {
Log.i(tag, "picture path: ${it.getString(indexPhotoPath)}")
}
it.close()
}
媒體文件可以通過 MediaStore 和 SAF 兩種方式訪問,但是非媒體文件只能通過 SAF 訪問,通過 SAF,用戶可以通過一個簡單的標準界面,以統(tǒng)一的方式瀏覽訪問文件。
這里以選擇 sdcard 目錄下的一個文本文件,對它進行讀寫操作為例。
private lateinit var startActivity: ActivityResultLauncher<Intent>
在 Activity 中注冊結(jié)果返回,這里需要注意的是,別等到 Activity 的生命周期執(zhí)行到 onResume 了才注冊,會報錯的,建議最好在 onCreate 中進行注冊,在這里拿到選擇的文件的 uri
startActivity =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.data != null && it.resultCode == Activity.RESULT_OK) {
readFileContent(it.data!!.data)
}
}
寫入內(nèi)容
private fun writeForUri(uri: Uri?) {
if (uri == null) return
try {
val outputStream = contentResolver.openOutputStream(uri)
val content = "Hello Android"
outputStream?.write(content.toByteArray())
outputStream?.flush()
outputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
讀取內(nèi)容
private fun readFileContent(uri: Uri?) {
if (uri == null) return
try {
val inputStream = contentResolver.openInputStream(uri) ?: return
val readContent = ByteArray(1024)
var len: Int
do {
len = inputStream.read(readContent)
if (len != -1) { //打印出文件內(nèi)容
Log.d(tag, "File Content: ${String(readContent).substring(0, len)}")
}
} while (len != -1)
inputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
打開文件選擇器
private fun openSAF() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
//指定選擇文本類型的文件
intent.type = "text/plain"
startActivity.launch(intent)
}
選擇器的用戶界面是這樣的,那個 NewTextFile.txt 就是我們操作的文本文件。
由此可見,SAF 提供了文件選擇器,調(diào)用者只需指定要讀寫的文件類型,比如文本類型,圖片類型,視頻類型等,選擇器就會過濾出相應(yīng)文件以供選擇,使用簡單。
Android 11
在 Android 11 中,不能直接獲取其他應(yīng)用的信息了,比如,查詢應(yīng)用信息的代碼如下:
val appList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (app in appList) {
Log.i(tag, "packageName: ${app.packageName}")
}
這段代碼只能查詢到自己應(yīng)用和系統(tǒng)應(yīng)用的信息,如果想要查詢其他應(yīng)用的信息,需要在 AndroidManifest 中添加對應(yīng)的包名配置。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> ... <queries> <package android:name="com.example.composeapp" /> </queries> ... </manifest>
如果你就是要獲取所有應(yīng)用的信息,怎么辦呢?Android 11 也提供了 QUERY_ALL_PACKAGES 權(quán)限,在 AndroidManifest 中加入即可。但是,加入該權(quán)限的時候會有紅線提示,建議使用上面的這種方式。加入該權(quán)限的 APP,應(yīng)用市場能不能過審,就很難說了。
Android 11 還增加了單次授權(quán),就是請求與位置信息,麥克風(fēng)或攝像頭相關(guān)的權(quán)限時,系統(tǒng)會自動提供一個單次授權(quán)的選項,只供這一次權(quán)限獲取,選擇它的話,用戶下次再次打開 APP 的時候,系統(tǒng)會再次提示用戶請求權(quán)限,所以,需要我們每次使用的時候去判斷一下權(quán)限,沒有就去申請。
Android 12
Android 12 增加了系統(tǒng)默認的 APP 啟動頁,這個啟動頁會使用 APP 定義的主題生成,這對我們的應(yīng)用影響還是比較大的,通常我們會用一個 Activity 作為啟動頁來顯示一些廣告推廣啥的,但是在 Android 12 上不適配的話,那用戶將會看到兩個閃屏。怎么去適配呢? Google 告訴我們,你可以選擇不管或者去掉 SplashActivity 并使用設(shè)置主題的方式來兼容,下面來看看設(shè)置主題的方式如何去實現(xiàn)?
implementation 'androidx.core:core-splashscreen:1.0.0'
<style name="Theme.App.Splash" parent="Theme.SplashScreen"> <!--特定的單色填充背景--> <item name="windowSplashScreenBackground">@color/white</item> <!--起始窗口中心的圖標--> <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item> <!--啟動畫面圖標動畫的時長,Google 建議不超過 1000 毫秒--> <item name="windowSplashScreenAnimationDuration">200</item> <!--必填項,SplashView 移除后使用此主題恢復(fù) Activity 樣式--> <item name="postSplashScreenTheme">@style/Theme.MyApplication</item> </style>
設(shè)置主題
<application ... android:theme="@style/Theme.App.Splash"> ... </application>
在啟動 Activity 中調(diào)用 installSplashScreen,注意要在 super.onCreate 之前添加。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
另外,Android 12 修改了根 Activity 在返回鍵的默認行為。在以前的版本中,返回鍵會執(zhí)行 finish Activity,而從 Android 12 開始會將任務(wù)棧切換到后臺,也就是說在根 Activity 點擊返回鍵時,生命周期只會執(zhí)行到 onStop,不執(zhí)行 onDestroy,所以,用戶返回應(yīng)用時將執(zhí)行溫啟動。
Android 13
從 Android 13 開始,用戶可以通過抽屜式通知欄完成工作流,以停止具有持續(xù)前臺服務(wù)的應(yīng)用,如下圖所示,此功能稱為前臺服務(wù) (FGS) 任務(wù)管理器,應(yīng)用必須能夠處理這種由用戶發(fā)起的停止操作。
此外,Android 13 引入了運行時通知權(quán)限:POST_NOTIFICATIONS, 如果拒絕這個權(quán)限的話,應(yīng)用將無法發(fā)送通知,此更改有助于用戶只關(guān)注自己認為重要的通知,但是與媒體會話以及自行管理通話的應(yīng)用相關(guān)的通知不受此行為變更的影響。
原文鏈接:https://juejin.cn/post/7178669690628079671
相關(guān)推薦
- 2022-04-28 Python可視化學(xué)習(xí)之seaborn調(diào)色盤_python
- 2023-01-08 簡化Cocos和Native交互利器詳解_React
- 2022-06-20 go語言實現(xiàn)屏幕截圖的示例代碼_Golang
- 2022-07-11 docker給正在運行中的容器添加映射端口
- 2023-10-09 git stash
- 2022-05-12 uni-app混合原生安卓開發(fā)
- 2022-05-22 部署ASP.NET?Core程序到Windows系統(tǒng)_基礎(chǔ)應(yīng)用
- 2022-11-05 WPF+ASP.NET?SignalR實現(xiàn)后臺通知功能的示例代碼_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支