日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Android不同版本兼容性適配方法教程_Android

作者:明智的健哥 ? 更新時間: 2022-12-05 編程語言

Android 6

運行時權限動態申請,這里推薦郭霖的開源庫:https://github.com/guolindev/PermissionX

Android 7

在Android 7.0系統上,禁止向你的應用外公開 file:// URI,如果一項包含文件 file:// URI類型的Intent離開你的應用,應用失敗,并出現 FileUriExposedException異常,如調用系統相機拍照。若要在應用間共享文件,可以發送 content:// URI類型的Uri,并授予URI 臨時訪問權限,使用FileProvider類。

使用FileProvider的大致步驟如下:

1.在res下創建xml目錄,在此目錄下創建file_paths.xml文件,內容如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- 內部存儲,等同于Context.getFilesDir,路徑:/data/data/包名/files目錄-->
    <files-path
        name="DocDir"
        path="/" />
    <!-- 內部存儲,等同于Context.getCacheDir,路徑:/data/data/包名/cache目錄-->
    <cache-path
        name="CacheDocDir"
        path="/" />
    <!--外部存儲,等同于Context.getExternalFilesDir,路徑:/storage/sdcard/Android/data/包名/files-->
    <external-files-path
        name="ExtDocDir"
        path="/" />
    <!--外部存儲,等同于Context.getExternalCacheDir 路徑:/storage/sdcard/Android/data/包名/cache-->
    <external-cache-path
        name="ExtCacheDir"
        path="/" />
</paths>

2.在manifest中注冊provider

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.unclexing.exploreapp.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <!--exported:要為false,為true則會報安全異常。grantUriPermissions為true,表示授予URI臨時訪問權限-->
            <meta-data
                android:name="androidx.core.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

3.使用FileProvider

            val file = File(
                getExternalFilesDir(null),
                "/temp/" + System.currentTimeMillis() + ".jpg"
            )
            if (!file.parentFile.exists()) {
                file.parentFile.mkdirs()
            }
            //通過FileProvider創建一個content類型的Uri
            val imageUri = FileProvider.getUriForFile(
                this,
                "com.unclexing.exploreapp.fileprovider", file
            )
            val intent = Intent()
            //表示對目標應用臨時授權該Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            intent.action = MediaStore.ACTION_IMAGE_CAPTURE
            //將拍攝的照片保存到特定的URI
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
            startActivity(intent)

Android 8

Android 8.0 引入了通知渠道,允許為要顯示的每種通知類型創建用戶可自定義的渠道,用戶界面將通知渠道稱之為通知類別。

    private fun createNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            //如果要分組,groupId要唯一
            val groupId = "group1"
            val group = NotificationChannelGroup(groupId, "advertisement")
            notificationManager.createNotificationChannelGroup(group)
            //channelId唯一
            val channelId = "channel1"
            val notificationChannel = NotificationChannel(
                channelId,
                "promote information",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            //將渠道添加進組,必須先創建組才能添加
            notificationChannel.group = groupId
            notificationManager.createNotificationChannel(notificationChannel)
            //創建通知
            val notification = Notification.Builder(this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
                .setContentTitle("A new notice")
                .setContentText("Likes and follows")
                .setAutoCancel(true)
                .build()
            notificationManager.notify(1, notification)
        }
    }

Android 8.0以后不允許后臺應用啟動后臺服務,需要通過startForegroundService()指定為前臺服務,應用有五秒的時間來調用該 Service 的 startForeground() 方法以顯示可見通知。 如果應用在此時間限制內未調用startForeground(),則系統將停止 Service 并聲明此應用為 ANR。

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val intent = Intent(this, UncleService::class.java)
                startForegroundService(intent)
            }
class UncleService : Service() {
    override fun onCreate() {
        super.onCreate()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            val channel =
                NotificationChannel("channelId", "channelName", NotificationManager.IMPORTANCE_HIGH)
            manager.createNotificationChannel(channel)
            val notification = Notification.Builder(this, "channelId")
                .build()
            startForeground(1, notification)
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        stopForeground(true)
    }
    override fun onBind(p0: Intent?): IBinder? {
        return null
    }
}

別忘了在manifest添加權限

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

Android 9

在Android 9中的網絡請求中,不允許使用http請求,要求使用https。

解決方案:

在 res 下新建一個xml目錄,然后創建一個名為:network_config.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在Mainfiests appliction標簽下配置該屬性

android:networkSecurityConfig="@xml/network_config"

這是一種簡單粗暴的方法,為了安全靈活,我們可以指定http域名,部分域名時使用http

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">csdn.example.com</domain>
    </domain-config>
</network-security-config>

Android 10

定位權限

用戶可以更好地控制應用何時可以訪問設備位置,當在Android 10上運行的應用程序請求位置訪問時,會通過對話框的形式給用戶進行授權提示,此時有兩種位置訪問權限:在使用中(僅限前臺)或始終(前臺和后臺)

新增權限 ACCESS_BACKGROUND_LOCATION

如果你的應用針對 Android 10并且需要在后臺運行時訪問用戶的位置,則必須在應用的清單文件中聲明新權限

  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

分區存儲

在Android 10之前的版本上,我們在做文件的操作時都會申請存儲空間的讀寫權限。但是這些權限完全被濫用,造成的問題就是手機的存儲空間中充斥著大量不明作用的文件,并且應用卸載后它也沒有刪除掉。為了解決這個問題,Android 10 中引入了分區存儲的概念,通過添加外部存儲訪問限制來實現更好的文件管理。但是應用得不徹底,因為我們可以在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true"來請求使用舊的存儲模式,以此來做簡單粗暴地適配。但是我不推薦這樣做,因為Android 11強制執行分區存儲機制,此配置已經將會失效,所以還得老老實實地做下適配,直接看下面Android 11的適配吧。

Android 11

無需存儲權限即可訪問的有兩種,一是App自身的內部存儲,一是App自身的自帶外部存儲。

對于存儲作用域訪問的區別就體現在如何訪問除此之外的目錄內的文件。

強制執行分區存儲

共享存儲空間存放的是圖片、視頻、音頻等文件,這些資源是公用的,所有App都能夠訪問它們。共享存儲空間里存放著圖片、視頻、音頻、下載的文件,App獲取或者插入文件的時候怎么區分這些類型呢?這個時候就需要MediaStore。比如想要查詢共享存儲空間里的圖片文件:

        val cursor = contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            null
        )

MediaStore.Images.Media.EXTERNAL_CONTENT_URI 意思是指定查詢文件的類型是圖片,并構造成Uri對象,Uri實現了Parcelable,能夠在進程間傳遞。

既然不能通過路徑直接訪問文件,那么如何通過Uri訪問文件呢?Uri可以通過MediaStore或SAF獲取。但是,需要注意的是:雖然也可以通過文件路徑直接構造Uri,但是此種方式構造的Uri是沒有權限訪問文件的。

現在我們來讀取/sdcard/目錄下的一個文本文件NewTextFile.txt,由于它不屬于共享存儲空間的文件,是屬于其它目錄的,因此不能通過MediaStore獲取,只能通過SAF獲取。

    private fun openSAF() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        //指定選擇文本類型的文件
        intent.type = "text/plain"
        startActivityForResult(intent, 1)
    }
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 1 && data != null) {
            val uri = data.data
            startRead(uri!!)
        }
    }
    private fun startRead(uri: Uri) {
        try {
            val inputStream = contentResolver.openInputStream(uri)
            val readContent = ByteArray(1024)
            var len: Int
            do {
                len = inputStream!!.read(readContent)
                if (len != -1) {
                    Log.d(tag, "file content:${String(readContent).substring(0, len)}")
                }
            } while (len != -1)
        } catch (e: Exception) {
            Log.d(tag, "Exception:${e.message}")
        }
    }

由此可以看出,屬于"其它目錄"下的文件,需要通過SAF訪問,SAF返回Uri,通過Uri構造InputStream即可讀取文件。

下面,我們再來寫入內容到該文件中,還是需要通過SAF拿到Uri,拿到Uri后構造輸出流。

    private fun writeForUri(uri: Uri) {
        try {
            val outputStream = contentResolver.openOutputStream(uri)
            val content = "my name is Uncle Xing"
            outputStream?.write(content.toByteArray())
            outputStream?.flush()
            outputStream?.close()
        } catch (e: Exception) {
            Log.d(tag, "Exception:${e.message}")
        }
    }

SAF好處是:系統提供了文件選擇器,調用者只需指定要讀寫的文件類型,比如文本類型、圖片類型、視頻類型等,選擇器就會過濾出相應文件以供選擇,使用簡單。

位置權限

Android 10請求ACCESS_FINE_LOCATION或 ACCESS_COARSE_LOCATION表示在前臺時擁有訪問設備位置信息的權限。在請求彈框還能看到始終允許,Android 11中,取消了始終允許選項,默認不會授予后臺訪問設備位置信息的權限。Android 11將后臺獲取設備位置信息抽離了出來,通過ACCESS_BACKGROUND_LOCATION權限后臺訪問設備位置信息的權限,需要注意的一點是,請求ACCESS_BACKGROUND_LOCATION的同時不能請求其它權限,否則系統會拋出異常。官方給出的建議是先請求前臺位置信息訪問權限,再請求后臺位置信息訪問權限。

原文鏈接:https://blog.csdn.net/qq_45485851/article/details/122417968

欄目分類
最近更新