欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 各版本兼容性適配詳解

 更新時(shí)間:2022年12月19日 15:31:15   作者:阿健叔  
這篇文章主要為大家介紹了Android 各版本兼容性適配詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Android 6

本文根據(jù)我個(gè)人的開發(fā)經(jīng)驗(yàn),總結(jié)了從 Android 6 - Android 13 重要的行為變更。當(dāng)然,這不是 Android 所有的行為變更,這里只是列舉了我覺得比較有影響的,比較常見的一些場景的開發(fā)適配。那么,下面讓我們一起來瞧瞧,有哪些行為變更是需要我們特別注意的。

在 Android 6 版本開始引進(jìn)運(yùn)行時(shí)權(quán)限機(jī)制,Android 將所有的權(quán)限歸為兩類,一類是普通權(quán)限,一類是危險(xiǎn)權(quán)限。普通權(quán)限一般不會(huì)威脅到用戶的安全和隱私,對(duì)于這部分權(quán)限,系統(tǒng)自動(dòng)對(duì)軟件進(jìn)行授權(quán),不需要詢問用戶。而危險(xiǎn)權(quán)限是可能對(duì)用戶的安全和隱私造成影響的權(quán)限,如獲取設(shè)備地理位置、獲取設(shè)備聯(lián)系人信息等,這些就需要明確通知用戶,并由用戶手動(dòng)進(jìn)行授權(quán)才可以進(jìn)行相應(yīng)操作。

危險(xiǎn)權(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(手機(jī))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(存儲(chǔ))READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE

運(yùn)行時(shí)權(quán)限動(dòng)態(tài)申請(qǐng),這里推薦郭神的開源庫 - PermissionX,使用簡單方便,這里不再贅述。

Android 7

Android 7 禁止向你的應(yīng)用外公開 file://URI, 如果在 Android 7 及以上系統(tǒng)傳遞 file:// URI 就會(huì)觸發(fā) FileUriExposedException,不適配的話在 Android 7 及以上系統(tǒng)就會(huì)出現(xiàn)應(yīng)用崩潰的現(xiàn)象。如果要在應(yīng)用間共享文件,可以發(fā)送 content://URI 類型的 URI,并授予 URI 臨時(shí)訪問權(quán)限,這就需要用到 FileProvider 類。

我們以調(diào)用系統(tǒng)相機(jī)拍照為例,在 res 下創(chuàng)建 xml 目錄,在此目錄下創(chuàng)建 file_paths.xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- 內(nèi)部存儲(chǔ),對(duì)應(yīng) filesDir,路徑:/data/data/package_name/files-->
    <files-path
        name="files_path"
        path="." />
    <!-- 內(nèi)部存儲(chǔ),對(duì)應(yīng) cacheDir,路徑:/data/data/package_name/cache-->
    <cache-path
        name="cache_path"
        path="." />
    <!--外部存儲(chǔ),對(duì)應(yīng) getExternalFilesDir,路徑:/storage/sdcard/Android/data/package_name/files-->
    <external-files-path
        name="external_files_path"
        path="." />
    <!--外部存儲(chǔ),對(duì)應(yīng) externalCacheDir,路徑:/storage/sdcard/Android/data/package_name/cache-->
    <external-cache-path
        name="external_cache_path"
        path="." />
</paths>

在 AndroidManifest 中注冊(cè) FileProvider

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.myapplication.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <!--exported 要為 false,否則會(huì)報(bào)安全異常,grantUriPermissions 為 true,表示授予 URI 臨時(shí)訪問權(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)建一個(gè) content 類型的 Uri
    val pictureUri =
        FileProvider.getUriForFile(
            this,
            "com.example.myapplication.fileProvider",
            pictureFile
        )
    intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri)
    // 授予目錄臨時(shí)共享權(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ī)定所有的通知必須分配一個(gè)渠道,每一個(gè)渠道,你都可以設(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 開始,不允許后臺(tái)應(yīng)用啟動(dòng)后臺(tái)服務(wù),需要使用 startForegroundService 指定為前臺(tái)服務(wù),否則系統(tǒng)會(huì)停止 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ò)請(qǐng)求,如果繼續(xù)使用 HTTP 請(qǐng)求,會(huì)在日志做出警告,不過只是無法正常發(fā)出請(qǐng)求,不會(huì)導(dǎo)致應(yīng)用崩潰。如果我們需要使用 HTTP 請(qǐng)求的話,需要在 AndroidManifest 中添加如下配置:

<application
    ...
    android:usesCleartextTraffic="true">
    ...
</application>

除了這個(gè)方法,我們也可以指定域名。在 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 之前的版本,我們?cè)谧鑫募牟僮鲿r(shí)都會(huì)申請(qǐng)存儲(chǔ)空間的讀寫權(quán)限,但是這些權(quán)限可能被濫用,造成手機(jī)的存儲(chǔ)空間中充斥著大量不明作用的文件,并且應(yīng)用卸載后也沒刪除掉。為了解決這個(gè)問題,Android 10 開始引入了分區(qū)存儲(chǔ)的概念。

分區(qū)存儲(chǔ)就是對(duì)外部存儲(chǔ)進(jìn)行了重新設(shè)計(jì),簡單來說,對(duì)于外部共享文件,需要通過 MediaStrore API 和 Storage Access Framework 來訪問,對(duì)于外部私有文件,無法讀寫自己應(yīng)用以外創(chuàng)建的其他文件。

Android 中存儲(chǔ)可以分為兩大類:專屬存儲(chǔ)和共享存儲(chǔ)。

  • 專屬存儲(chǔ):每個(gè)應(yīng)用在都擁有自己的專屬目錄,其它應(yīng)用看不到。它包括 APP 自身的內(nèi)部存儲(chǔ)和外部存儲(chǔ),這倆無需存儲(chǔ)權(quán)限便可訪問。
  • 共享存儲(chǔ):共享存儲(chǔ)空間存放的是圖片,視頻和音頻等文件,這些資源是公共的,所有 App 都能訪問它們。

舉個(gè)例子,如果想拿到共享存儲(chǔ)里的圖片路徑,該怎么做呢?

首先需要申請(qǐng)權(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,用戶可以通過一個(gè)簡單的標(biāo)準(zhǔn)界面,以統(tǒng)一的方式瀏覽訪問文件。

這里以選擇 sdcard 目錄下的一個(gè)文本文件,對(duì)它進(jìn)行讀寫操作為例。

private lateinit var startActivity: ActivityResultLauncher&lt;Intent&gt;

在 Activity 中注冊(cè)結(jié)果返回,這里需要注意的是,別等到 Activity 的生命周期執(zhí)行到 onResume 了才注冊(cè),會(huì)報(bào)錯(cuò)的,建議最好在 onCreate 中進(jìn)行注冊(cè),在這里拿到選擇的文件的 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)
    }

選擇器的用戶界面是這樣的,那個(gè) NewTextFile.txt 就是我們操作的文本文件。

由此可見,SAF 提供了文件選擇器,調(diào)用者只需指定要讀寫的文件類型,比如文本類型,圖片類型,視頻類型等,選擇器就會(huì)過濾出相應(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 中添加對(duì)應(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)限的時(shí)候會(huì)有紅線提示,建議使用上面的這種方式。加入該權(quán)限的 APP,應(yīng)用市場能不能過審,就很難說了。

Android 11 還增加了單次授權(quán),就是請(qǐng)求與位置信息,麥克風(fēng)或攝像頭相關(guān)的權(quán)限時(shí),系統(tǒng)會(huì)自動(dòng)提供一個(gè)單次授權(quán)的選項(xiàng),只供這一次權(quán)限獲取,選擇它的話,用戶下次再次打開 APP 的時(shí)候,系統(tǒng)會(huì)再次提示用戶請(qǐng)求權(quán)限,所以,需要我們每次使用的時(shí)候去判斷一下權(quán)限,沒有就去申請(qǐng)。

Android 12

Android 12 增加了系統(tǒng)默認(rèn)的 APP 啟動(dòng)頁,這個(gè)啟動(dòng)頁會(huì)使用 APP 定義的主題生成,這對(duì)我們的應(yīng)用影響還是比較大的,通常我們會(huì)用一個(gè) Activity 作為啟動(dòng)頁來顯示一些廣告推廣啥的,但是在 Android 12 上不適配的話,那用戶將會(huì)看到兩個(gè)閃屏。怎么去適配呢? Google 告訴我們,你可以選擇不管或者去掉 SplashActivity 并使用設(shè)置主題的方式來兼容,下面來看看設(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>
    <!--起始窗口中心的圖標(biāo)-->
    <item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
    <!--啟動(dòng)畫面圖標(biāo)動(dòng)畫的時(shí)長,Google 建議不超過 1000 毫秒-->
    <item name="windowSplashScreenAnimationDuration">200</item>
    <!--必填項(xiàng),SplashView 移除后使用此主題恢復(fù) Activity 樣式-->
    <item name="postSplashScreenTheme">@style/Theme.MyApplication</item>
</style>

設(shè)置主題

<application
    ...
    android:theme="@style/Theme.App.Splash">
    ...
</application>

在啟動(dòng) 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 在返回鍵的默認(rèn)行為。在以前的版本中,返回鍵會(huì)執(zhí)行 finish Activity,而從 Android 12 開始會(huì)將任務(wù)棧切換到后臺(tái),也就是說在根 Activity 點(diǎn)擊返回鍵時(shí),生命周期只會(huì)執(zhí)行到 onStop,不執(zhí)行 onDestroy,所以,用戶返回應(yīng)用時(shí)將執(zhí)行溫啟動(dòng)。

Android 13

從 Android 13 開始,用戶可以通過抽屜式通知欄完成工作流,以停止具有持續(xù)前臺(tái)服務(wù)的應(yīng)用,如下圖所示,此功能稱為前臺(tái)服務(wù) (FGS) 任務(wù)管理器,應(yīng)用必須能夠處理這種由用戶發(fā)起的停止操作。

此外,Android 13 引入了運(yùn)行時(shí)通知權(quán)限:POST_NOTIFICATIONS, 如果拒絕這個(gè)權(quán)限的話,應(yīng)用將無法發(fā)送通知,此更改有助于用戶只關(guān)注自己認(rèn)為重要的通知,但是與媒體會(huì)話以及自行管理通話的應(yīng)用相關(guān)的通知不受此行為變更的影響。

以上就是Android 各版本兼容性適配詳解的詳細(xì)內(nèi)容,更多關(guān)于Android 版本兼容性適配的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法

    Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法

    這篇文章主要為大家詳細(xì)介紹了Android Button點(diǎn)擊事件的四種實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Android下拉刷新控件SwipeRefreshLayout源碼解析

    Android下拉刷新控件SwipeRefreshLayout源碼解析

    這篇文章主要為大家詳細(xì)解析Android下拉刷新控件SwipeRefreshLayout源碼,感興趣的小伙伴們可以參考一下
    2016-07-07
  • kotlin Context使用詳解

    kotlin Context使用詳解

    這篇文章主要介紹了kotlin Context使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03
  • flutter的環(huán)境安裝配置問題及解決方法

    flutter的環(huán)境安裝配置問題及解決方法

    Flutter是Google推出的基于Dart語言開發(fā)的跨平臺(tái)開源UI框架,旨在統(tǒng)一紛紛擾擾的跨平臺(tái)開發(fā)框架,在UI層面上多端共用一套Dart代碼來實(shí)現(xiàn)多平臺(tái)適配開發(fā),這篇文章主要介紹了flutter的環(huán)境安裝配置問題,需要的朋友可以參考下
    2020-06-06
  • Kotlin方法與Lambda表達(dá)式實(shí)踐使用介紹

    Kotlin方法與Lambda表達(dá)式實(shí)踐使用介紹

    這篇文章主要介紹了Kotlin方法與Lambda表達(dá)式實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • Android開發(fā)之圖形圖像與動(dòng)畫(四)AnimationListener簡介

    Android開發(fā)之圖形圖像與動(dòng)畫(四)AnimationListener簡介

    就像Button控件有監(jiān)聽器一樣,動(dòng)畫效果也有監(jiān)聽器,只需要實(shí)現(xiàn)AnimationListener就可以實(shí)現(xiàn)對(duì)動(dòng)畫效果的監(jiān)聽,感興趣的朋友可以了解下啊,希望本文對(duì)你有所幫助
    2013-01-01
  • Android開發(fā)之使用SQLite存儲(chǔ)數(shù)據(jù)的方法分析

    Android開發(fā)之使用SQLite存儲(chǔ)數(shù)據(jù)的方法分析

    這篇文章主要介紹了Android開發(fā)之使用SQLite存儲(chǔ)數(shù)據(jù)的方法,結(jié)合實(shí)例形式分析了Android使用SQLite數(shù)據(jù)庫實(shí)現(xiàn)針對(duì)數(shù)據(jù)的增刪改查操作相關(guān)技巧,需要的朋友可以參考下
    2017-07-07
  • 老生常談ProgressBar、ProgessDialog的用法

    老生常談ProgressBar、ProgessDialog的用法

    下面小編就為大家?guī)硪黄仙U凱rogressBar、ProgessDialog的用法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • Android短信發(fā)送器實(shí)現(xiàn)方法

    Android短信發(fā)送器實(shí)現(xiàn)方法

    這篇文章主要介紹了Android短信發(fā)送器實(shí)現(xiàn)方法,以實(shí)例形式較為詳細(xì)的分析了Android短信發(fā)送器從界面布局到功能實(shí)現(xiàn)的完整步驟與相關(guān)技巧,需要的朋友可以參考下
    2015-09-09
  • WindowManagerService服務(wù)是如何以堆棧的形式來組織窗口

    WindowManagerService服務(wù)是如何以堆棧的形式來組織窗口

    我們知道,在Android系統(tǒng)中,Activity是以堆棧的形式組織在ActivityManagerService服務(wù)中的;在本文中,我們就詳細(xì)分析WindowManagerService服務(wù)是如何以堆棧的形式來組織窗口的
    2013-01-01

最新評(píng)論