這篇文章主要介紹了Android10適配的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)公司成立10年來,這條路我們正越走越好,積累了技術(shù)與客戶資源,形成了良好的口碑。為客戶提供網(wǎng)站建設(shè)、網(wǎng)站制作、網(wǎng)站策劃、網(wǎng)頁設(shè)計(jì)、域名申請、網(wǎng)絡(luò)營銷、VI設(shè)計(jì)、網(wǎng)站改版、漏洞修補(bǔ)等服務(wù)。網(wǎng)站是否美觀、功能強(qiáng)大、用戶體驗(yàn)好、性價(jià)比高、打開快等等,這些對于網(wǎng)站建設(shè)都非常重要,創(chuàng)新互聯(lián)公司通過對建站技術(shù)性的掌握、對創(chuàng)意設(shè)計(jì)的研究為客戶提供一站式互聯(lián)網(wǎng)解決方案,攜手廣大客戶,共同發(fā)展進(jìn)步。
老規(guī)矩,首先將我們項(xiàng)目中的 targetSdkVersion
改為 29。
在Android 10之前的版本上,我們在做文件的操作時(shí)都會(huì)申請存儲(chǔ)空間的讀寫權(quán)限。但是這些權(quán)限完全被濫用,造成的問題就是手機(jī)的存儲(chǔ)空間中充斥著大量不明作用的文件,并且應(yīng)用卸載后它也沒有刪除掉。為了解決這個(gè)問題,Android 10 中引入了 Scoped Storage
的概念,通過添加外部存儲(chǔ)訪問限制來實(shí)現(xiàn)更好的文件管理。
首先明確一個(gè)概念,外部儲(chǔ)存和內(nèi)部儲(chǔ)存。
內(nèi)部儲(chǔ)存: /data
目錄。一般我們使用 getFilesDir()
或 getCacheDir()
方法獲取本應(yīng)用的內(nèi)部儲(chǔ)存路徑,讀寫該路徑下的文件不需要申請儲(chǔ)存空間讀寫權(quán)限,且卸載應(yīng)用時(shí)會(huì)自動(dòng)刪除。
外部儲(chǔ)存: /storage
或 /mnt
目錄。一般我們使用 getExternalStorageDirectory()
方法獲取的路徑來存取文件。
因?yàn)椴煌瑥S商、系統(tǒng)版本的原因,所以上述的方法并沒有一個(gè)固定的文件路徑。了解了上面的概念,那我們所說的外部儲(chǔ)存訪問限制,可以認(rèn)為是針對 getExternalStorageDirectory()
路徑下的文件。具體的規(guī)則如下表:
上圖將外部存儲(chǔ)空間分為了三部分:
特定目錄(App-specific),使用 getExternalFilesDir()
或 getExternalCacheDir()
方法訪問。無需權(quán)限,且卸載應(yīng)用時(shí)會(huì)自動(dòng)刪除。
照片、視頻、音頻這類媒體文件。使用 MediaStore
訪問,訪問其他應(yīng)用的媒體文件時(shí)需要 READ_EXTERNAL_STORAGE
權(quán)限。
其他目錄,使用 存儲(chǔ)訪問框架SAF (Storage Access Framwork)
所以在Android 10上即使你擁有了儲(chǔ)存空間的讀寫權(quán)限,也無法保證可以正常的進(jìn)行文件的讀寫操作。
適配
最簡單粗暴的方法就是在 AndroidManifest.xml
中添加 android:requestLegacyExternalStorage="true"
來請求使用舊的存儲(chǔ)模式。
但是我不推薦此方法。因?yàn)樵谙乱粋€(gè)版本的Android中,此條配置將會(huì)失效,將強(qiáng)制采用外部儲(chǔ)存限制。其實(shí)早在Android Q Beta 3之前都是強(qiáng)制的,但為了給開發(fā)者適配的時(shí)間才沒有強(qiáng)制執(zhí)行。所以如果你不抓住這段時(shí)間去適配,那么今年下半年出了Android 11。。。直接開花~~
如果你已經(jīng)適配Android 10,這里有個(gè)現(xiàn)象要 注意一下:
如果應(yīng)用通過升級安裝,那么還會(huì)使用以前的儲(chǔ)存模式(Legacy View)。只有通過首次安裝或是卸載重新安裝才能啟用新模式(Filtered View)。
所以在適配時(shí),我們的判斷代碼如下:
// 使用Environment.isExternalStorageLegacy()來檢查APP的運(yùn)行模式 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !Environment.isExternalStorageLegacy()) { }
這樣的好處是你可以在用戶升級后,能方便的將用戶的數(shù)據(jù)移動(dòng)至應(yīng)用的特定目錄。否則你只能通過SAF去移動(dòng),這樣會(huì)非常麻煩。如果你要移動(dòng)數(shù)據(jù)注意只適用于Android 10下,所以現(xiàn)在適配反而是一個(gè)好時(shí)機(jī)。當(dāng)然如果你不需要遷移數(shù)據(jù),那適配會(huì)更省事。
下面就說說推薦適配方案:
對于應(yīng)用中涉及的文件操作,修改一下你的文件路徑。
以前我們習(xí)慣使用 Environment.getExternalStorageDirectory()
方法,那么現(xiàn)在可以使用 getExternalFilesDir()
方法(包括下載的安裝包這類的文件)。如果是緩存類型文件,可以放到 getExternalCacheDir()
路徑下。
或者使用 MediaStore
,將文件存至對應(yīng)的媒體類型中(圖片: MediaStore.Images
,視頻: MediaStore.Video
,音頻: MediaStore.Audio
),不過僅限于多媒體文件。
下面代碼將圖片保存到公共目錄下,返回Uri:
public static Uri createImageUri(Context context) { ContentValues values = new ContentValues(); // 需要指定文件信息時(shí),非必須 values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image"); values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); values.put(MediaStore.Images.Media.TITLE, "Image.png"); values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test"); return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); }
對于媒體資源的訪問:比如圖片選擇器這類的場景。無法直接使用File,而應(yīng)使用Uri。否則報(bào)錯(cuò)如下:
java.io.FileNotFoundException: open failed: EACCES (Permission denied)
比如我在適配項(xiàng)目中使用的圖片選擇器時(shí),首先修改了 Glide
通過加載File的方式顯示圖片。改為加載Uri的方式,否則圖片無法顯示出來。
Uri的獲取方式還是使用 MediaStore
:
String id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
其次為了便于不影響之前選擇圖片返回File的邏輯(因?yàn)橐话愣际巧蟼鱂ile,沒有直接上傳Uri的操作),所以我將最終選擇的文件又轉(zhuǎn)存進(jìn)了 getExternalFilesDir()
,主要代碼如下:
File imgFile = this.getExternalFilesDir("image"); if (!imgFile.exists()){ imgFile.mkdir(); } try { File file = new File(imgFile.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg"); // 使用openInputStream(uri)方法獲取字節(jié)輸入流 InputStream fileInputStream = getContentResolver().openInputStream(uri); FileOutputStream fileOutputStream = new FileOutputStream(file); byte[] buffer = new byte[1024]; int byteRead; while (-1 != (byteRead = fileInputStream.read(buffer))) { fileOutputStream.write(buffer, 0, byteRead); } fileInputStream.close(); fileOutputStream.flush(); fileOutputStream.close(); // 文件可用新路徑 file.getAbsolutePath() } catch (Exception e) { e.printStackTrace(); }
如果你要獲取圖片中的地理位置信息,需要申請 ACCESS_MEDIA_LOCATION
權(quán)限,并使用MediaStore.setRequireOriginal()獲取。下面是官方的示例代碼:
Uri photoUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex)); final double[] latLong; // 從ExifInterface類獲取位置信息 photoUri = MediaStore.setRequireOriginal(photoUri); InputStream stream = getContentResolver().openInputStream(photoUri); if (stream != null) { ExifInterface exifInterface = new ExifInterface(stream); double[] returnedLatLong = exifInterface.getLatLong(); // If lat/long is null, fall back to the coordinates (0, 0). latLong = returnedLatLong != null ? returnedLatLong : new double[2]; // Don't reuse the stream associated with the instance of "ExifInterface". stream.close(); } else { // Failed to load the stream, so return the coordinates (0, 0). latLong = new double[2]; }
這樣下來,一個(gè)圖片選擇器就基本適配完了。
補(bǔ)充
應(yīng)用在卸載后,會(huì)將 App-specific
目錄下的數(shù)據(jù)刪除,如果在 AndroidManifest.xml
中聲明: android:hasFragileUserData="true"
用戶可以選擇是否保留。
對于 SAF
的使用,可以查看我之前寫的 SAF使用攻略 ,這里就不展開說了。
最后這里有一個(gè)介紹Scoped Storage的視頻,推薦 觀看 :
從6.0開始,基本每次都會(huì)有權(quán)限方面變動(dòng),這次也不例外。(前幾天發(fā)布了Android 11的預(yù)覽版,看來也有權(quán)限方面的變化。。。單次權(quán)限即將到來)
1.在后臺(tái)運(yùn)行時(shí)訪問設(shè)備位置信息需要權(quán)限
Android 10 引入了 ACCESS_BACKGROUND_LOCATION
權(quán)限(危險(xiǎn)權(quán)限)。
該權(quán)限允許應(yīng)用程序在后臺(tái)訪問位置。如果請求此權(quán)限,則還必須請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權(quán)限。只請求此權(quán)限無效果。
在Android 10的設(shè)備上,如果你的應(yīng)用的 targetSdkVersion
< 29,則在請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權(quán)限時(shí),系統(tǒng)會(huì)自動(dòng)同時(shí)請求 ACCESS_BACKGROUND_LOCATION
。在請求彈框中,選擇“始終允許”表示同意后臺(tái)獲取位置信息,選擇“僅在應(yīng)用使用過程中允許”或"拒絕"選項(xiàng)表示拒絕授權(quán)。
如果你的應(yīng)用的 targetSdkVersion
>= 29,則請求 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
權(quán)限表示在前臺(tái)時(shí)擁有訪問設(shè)備位置信息的權(quán)。在請求彈框中,選擇“始終允許”表示前后臺(tái)都可以獲取位置信息,選擇“僅在應(yīng)用使用過程中允許”只表示擁有前臺(tái)的權(quán)限。
總結(jié)一下就是下圖:
其實(shí)官方 不推薦你使用申請后臺(tái)訪問權(quán)的方式,因?yàn)檫@樣的結(jié)果無非就是多請求一個(gè)權(quán)限,那么這像變更還有什么意義?申請過多的權(quán)限,也會(huì)造成用戶的反感。所以官方推薦使用 前臺(tái)服務(wù)
來實(shí)現(xiàn),在前臺(tái)服務(wù)中獲取位置信息。
首先在清單中對應(yīng)的 service
中添加 android:foregroundServiceType="location"
:
...
啟動(dòng)前臺(tái)服務(wù)前檢查是否具有前臺(tái)的訪問權(quán)限:
boolean permissionApproved = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; if (permissionApproved) { // 啟動(dòng)前臺(tái)服務(wù) } else { // 請求前臺(tái)訪問位置權(quán)限 }
如此一來就可以在 Service
中獲取位置信息。
2.一些電話、藍(lán)牙和WLAN的API需要精確位置權(quán)限
下面列舉了Android 10中必須具有 ACCESS_FINE_LOCATION
權(quán)限才能使用類和方法:
電話
TelephonyManager
getCellLocation()
getAllCellInfo()
requestNetworkScan()
requestCellInfoUpdate()
getAvailableNetworks()
getServiceState()
TelephonyScanManager
requestNetworkScan()
TelephonyScanManager.NetworkScanCallback
onResults()
PhoneStateListener
onCellLocationChanged()
onCellInfoChanged()
onServiceStateChanged()
WLAN
WifiManager
startScan()
getScanResults()
getConnectionInfo()
getConfiguredNetworks()
WifiAwareManager
WifiP2pManager
WifiRttManager
藍(lán)牙
BluetoothAdapter
startDiscovery()
startLeScan()
BluetoothAdapter.LeScanCallback
BluetoothLeScanner
startScan()
我們可以根據(jù)上面提供的具體類和方法,在適配項(xiàng)目中檢查是否有使用到并及時(shí)處理。
3.ACCESS_MEDIA_LOCATION
Android 10新增權(quán)限,上面有提到,不贅述了。
4.PROCESS_OUTGOING_CALLS
Android 10上該權(quán)限已廢棄。
簡單解釋就是 應(yīng)用處于后臺(tái)時(shí),無法啟動(dòng)Activity。比如點(diǎn)開一個(gè)應(yīng)用會(huì)進(jìn)入啟動(dòng)頁或者廣告頁,一般會(huì)有幾秒的延時(shí)再跳轉(zhuǎn)至首頁。如果這期間你退到后臺(tái),那么你將無法看到跳轉(zhuǎn)過程。而在之前的版本中,會(huì)強(qiáng)制彈出頁面至前臺(tái)。
既然是限制,那么肯定有不受限的情況,主要有以下幾點(diǎn):
應(yīng)用具有可見窗口,例如前臺(tái) Activity。
應(yīng)用在前臺(tái)任務(wù)的返回棧中已有的 Activity。
應(yīng)用在 Recents
上現(xiàn)有任務(wù)的返回棧中已有的 Activity。 Recents
就是我們的任務(wù)管理列表。
應(yīng)用收到系統(tǒng)的 PendingIntent
通知。
應(yīng)用收到它應(yīng)該在其中啟動(dòng)界面的系統(tǒng)廣播。示例包括 ACTION_NEW_OUTGOING_CALL
和 SECRET_CODE_ACTION
。應(yīng)用可在廣播發(fā)送幾秒鐘后啟動(dòng) Activity。
用戶已向應(yīng)用授予 SYSTEM_ALERT_WINDOW
權(quán)限,或是在應(yīng)用權(quán)限頁開啟 后臺(tái)彈出頁面
的開關(guān)。
因?yàn)榇隧?xiàng)行為變更適用于在 Android 10 上運(yùn)行的所有應(yīng)用,所以這一限制導(dǎo)致最明顯的問題就是點(diǎn)擊推送信息時(shí),有些應(yīng)用無法進(jìn)行正常的跳轉(zhuǎn)(具體的實(shí)現(xiàn)問題導(dǎo)致)。所以針對這類問題,可以采取 PendingIntent
的方式,發(fā)送通知時(shí)使用 setContentIntent
方法。
當(dāng)然你也可以申請相應(yīng)權(quán)限或者白名單:
不過申請白名單這種方法受各種手機(jī)廠商所限,很麻煩。感覺還不如引導(dǎo)用戶手動(dòng)開啟權(quán)限。。。
對于全屏 intent,注意設(shè)置最高優(yōu)先級和添加 USE_FULL_SCREEN_INTENT
權(quán)限,這是一個(gè)普通權(quán)限。比如微信來語音或者視頻通話時(shí),彈出的接聽頁面就是使用這一功能。
Intent fullScreenIntent = new Intent(this, CallActivity.class); PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle("Incoming call") .setContentText("(919) 555-1234") .setPriority(NotificationCompat.PRIORITY_HIGH) // <--- 高優(yōu)先級 .setCategory(NotificationCompat.CATEGORY_CALL) // Use a full-screen intent only for the highest-priority alerts where you // have an associated activity that you would like to launch after the user // interacts with the notification. Also, if your app targets Android 10 // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in // order for the platform to invoke this notification. .setFullScreenIntent(fullScreenPendingIntent, true); // <--- 全屏 intent Notification incomingCallNotification = notificationBuilder.build();
注意:在部分手機(jī)上,直接設(shè)置 setPriority
無效(或者說以渠道優(yōu)先級為準(zhǔn))。所以需要?jiǎng)?chuàng)建通知渠道時(shí)將重要性設(shè)置為 IMPORTANCE_HIGH
。
NotificationChannel channel = new NotificationChannel(channelId, "xxx", NotificationManager.IMPORTANCE_HIGH);
后臺(tái)啟動(dòng) Activity 的限制的目的是為了減少對用戶操作的中斷。如果你有要彈出的頁面,推薦你先彈出通知,讓用戶自己選擇接下來的操作,而不是一股腦的強(qiáng)制彈出。(如果你的全屏intent都讓用戶反感,那他也可以關(guān)掉你的通知,不至于任你擺布。)
Android 10 新增了一個(gè)系統(tǒng)級的深色主題(在系統(tǒng)設(shè)置中開啟)。雖然深色主題并不是強(qiáng)制適配項(xiàng),但是它可以帶給用戶更好的體驗(yàn):
可大幅減少耗電量。 OLED
屏幕中每個(gè)像素都是自主發(fā)光,所以在顯示深色元素時(shí)像素所消耗的電流更低,尤其在純黑顏色時(shí)像素點(diǎn)可以完全關(guān)閉來達(dá)到省電的效果。
為弱視以及對強(qiáng)光敏感的用戶提高可視性。深色可以降低屏幕的整體視覺亮度,減少對眼睛的視覺壓力。
讓所有人都可以在光線較暗的環(huán)境中更輕松地使用設(shè)備。
適配方法有兩種:
1.手動(dòng)適配(資源替換)
官方文檔中提到的繼承 Theme.AppCompat.DayNight
或者 Theme.MaterialComponents.DayNight
的方法,但這只是將我們使用的各種View的默認(rèn)樣式進(jìn)行了適配,并不太適用于實(shí)際項(xiàng)目的適配。因?yàn)榫唧w的項(xiàng)目中的View都按照設(shè)計(jì)的風(fēng)格進(jìn)行了重定義。
其實(shí)適配的方法很簡單,類似屏幕適配、國際化的操作,并不需要繼承上面的主題。比如你要修改顏色,就在 res
下新建 values-night
目錄,創(chuàng)建對應(yīng)的 colors.xml
文件。將具體要修改的色值定義在里面。圖標(biāo)之類的也是一個(gè)思路,創(chuàng)建對應(yīng)的 drawable-night
目錄。
只要你之前的代碼不是硬編碼且代碼規(guī)范,那么適配起來還是很輕松。
2.自動(dòng)適配(Force Dark)
Android 10 提供 Force Dark 功能。一如其名,此功能可讓開發(fā)者快速實(shí)現(xiàn)深色主題背景,而無需明確設(shè)置 DayNight 主題背景。
如果您的應(yīng)用采用淺色主題背景,則 Force Dark 會(huì)分析應(yīng)用的每個(gè)視圖,并在相應(yīng)視圖在屏幕上顯示之前,自動(dòng)應(yīng)用深色主題背景。有些開發(fā)者會(huì)混合使用 Force Dark 和本機(jī)實(shí)現(xiàn),以縮短實(shí)現(xiàn)深色主題背景所需的時(shí)間。
應(yīng)用必須選擇啟用 Force Dark,方法是在其主題背景中設(shè)置 android:forceDarkAllowed="true"
。此屬性會(huì)在所有系統(tǒng)及 AndroidX 提供的淺色主題背景(例如 Theme.Material.Light)上設(shè)置。使用 Force Dark 時(shí),您應(yīng)確保全面測試應(yīng)用,并根據(jù)需要排除視圖。
如果您的應(yīng)用使用 Dark Theme
主題(例如Theme.Material),則系統(tǒng)不會(huì)應(yīng)用 Force Dark。同樣,如果應(yīng)用的主題背景繼承自 DayNight
主題(例如Theme.AppCompat.DayNight),則系統(tǒng)不會(huì)應(yīng)用 Force Dark,因?yàn)闀?huì)自動(dòng)切換主題背景。
您可以通過 android:forceDarkAllowed
布局屬性或 setForceDarkAllowed(boolean)
在特定視圖上控制 Force Dark。
上述內(nèi)容我直接照搬文檔的說明??偨Y(jié)一下,使用 Force Dark
需要注意幾點(diǎn):
如果使用的是 DayNight
或 Dark Theme
主題,則設(shè)置 forceDarkAllowed
不生效。
如果有需要排除適配的部分,可以在對應(yīng)的View上設(shè)置 forceDarkAllowed
為false。
這里說說我實(shí)際使用此方法的感受: 整體還是不錯(cuò)的,設(shè)置的色值會(huì)自動(dòng)取反。但也因此顏色不受控制,能否達(dá)到預(yù)期效果是個(gè)需要注意的問題。追求快速適配可以采取此方案。
手動(dòng)切換主題
使用 AppCompatDelegate.setDefaultNightMode(@NightMode int mode)
方法,其中參數(shù) mode
有以下幾種:
淺色 - MODE_NIGHT_NO
深色 - MODE_NIGHT_YES
由省電模式設(shè)置 - MODE_NIGHT_AUTO_BATTERY
系統(tǒng)默認(rèn) - MODE_NIGHT_FOLLOW_SYSTEM
下面的代碼是官方Demo中的使用示例:
public class ThemeHelper { public static final String LIGHT_MODE = "light"; public static final String DARK_MODE = "dark"; public static final String DEFAULT_MODE = "default"; public static void applyTheme(@NonNull String themePref) { switch (themePref) { case LIGHT_MODE: { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); break; } case DARK_MODE: { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); break; } default: { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY); } break; } } } }
通過 AppCompatDelegate.getDefaultNightMode()
方法,可以獲取到當(dāng)前的模式,這樣便于代碼中去適配。
監(jiān)聽深色主題是否開啟
首先在清單文件中給對應(yīng)的Activity配置 android:configChanges="uiMode"
:
這樣在 onConfigurationChanged
方法中就可以獲?。?/p>
@Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (currentNightMode) { case Configuration.UI_MODE_NIGHT_NO: // 關(guān)閉 break; case Configuration.UI_MODE_NIGHT_YES: // 開啟 break; default: break; } }
詳細(xì)的內(nèi)容你可以參看官方文檔 和官方Demo 。
判斷深色主題是否開啟
其實(shí)和上面 onConfigurationChanged
方法同理:
public static boolean isNightMode(Context context) { int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; return currentNightMode == Configuration.UI_MODE_NIGHT_YES; }
對不可重置的設(shè)備標(biāo)識(shí)符實(shí)施了限制
受影響的方法包括:
Build
getSerial()
TelephonyManager
getImei()
getDeviceId()
getMeid()
getSimSerialNumber()
getSubscriberId()
從 Android 10 開始,應(yīng)用必須具有 READ_PRIVILEGED_PHONE_STATE
特許權(quán)限才能正常使用以上這些方法。
如果你的應(yīng)用沒有該權(quán)限,卻仍然使用了以上的方法,則返回的結(jié)果會(huì)因目標(biāo) SDK 版本而異:
如果應(yīng)用以 Android 10 或更高版本為目標(biāo)平臺(tái),則會(huì)發(fā)生 SecurityException
。
如果應(yīng)用以 Android 9(API 級別 28)或更低版本為目標(biāo)平臺(tái),則相應(yīng)方法會(huì)返回 null 或占位符數(shù)據(jù)(如果應(yīng)用具有 READ_PHONE_STATE
權(quán)限)。否則,會(huì)發(fā)生 SecurityException
。
這項(xiàng)改動(dòng)表示第三方應(yīng)用無法獲取 Device ID
這類唯一標(biāo)識(shí)。如果你需要唯一標(biāo)識(shí)符,請參閱文檔: 唯一標(biāo)識(shí)符的最佳做法 。
當(dāng)然你也可以試試移動(dòng)安全聯(lián)盟(MSA)聯(lián)合多家廠商共同開發(fā)的 統(tǒng)一補(bǔ)充設(shè)備標(biāo)識(shí)調(diào)用SDK 。據(jù)說還有點(diǎn)不穩(wěn)定,因?yàn)槲視簳r(shí)還沒有嘗試過,所以不做評價(jià)。
限制了對剪貼板數(shù)據(jù)的訪問權(quán)限
除非您的應(yīng)用是默認(rèn)輸入法 (IME) 或是目前處于焦點(diǎn)的應(yīng)用,否則它無法訪問 Android 10 或更高版本平臺(tái)上的剪貼板數(shù)據(jù)。
對啟用和停用 WLAN 實(shí)施了限制
以 Android 10 或更高版本為目標(biāo)平臺(tái)的應(yīng)用無法啟用或停用 WLAN。
WifiManager.setWifiEnabled()方法始終返回 false。
如果您需要提示用戶啟用或停用 WLAN,請使用設(shè)置面板。
Android10上對折疊屏設(shè)備有了更好的支持,對于有折疊屏適配的需求,可以參看為可折疊設(shè)備構(gòu)建應(yīng)用 和 華為折疊屏應(yīng)用開發(fā)指導(dǎo)。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Android10適配的示例分析”這篇文章對大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!