【移動應用開發(fā)技術(shù)】Android10適配的示例分析_第1頁
【移動應用開發(fā)技術(shù)】Android10適配的示例分析_第2頁
【移動應用開發(fā)技術(shù)】Android10適配的示例分析_第3頁
【移動應用開發(fā)技術(shù)】Android10適配的示例分析_第4頁
【移動應用開發(fā)技術(shù)】Android10適配的示例分析_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

【移動應用開發(fā)技術(shù)】Android10適配的示例分析

這篇文章主要介紹了Android10適配的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓在下帶著大家一起了解一下。準備工作老規(guī)矩,首先將我們項目中的targetSdkVersion改為29。1.ScopedStorage(分區(qū)存儲)說明在Android10之前的版本上,我們在做文件的操作時都會申請存儲空間的讀寫權(quán)限。但是這些權(quán)限完全被濫用,造成的問題就是手機的存儲空間中充斥著大量不明作用的文件,并且應用卸載后它也沒有刪除掉。為了解決這個問題,Android10中引入了ScopedStorage的概念,通過添加外部存儲訪問限制來實現(xiàn)更好的文件管理。首先明確一個概念,外部儲存和內(nèi)部儲存。內(nèi)部儲存:/data目錄。一般我們使用getFilesDir()或getCacheDir()方法獲取本應用的內(nèi)部儲存路徑,讀寫該路徑下的文件不需要申請儲存空間讀寫權(quán)限,且卸載應用時會自動刪除。外部儲存:/storage或/mnt目錄。一般我們使用getExternalStorageDirectory()方法獲取的路徑來存取文件。因為不同廠商、系統(tǒng)版本的原因,所以上述的方法并沒有一個固定的文件路徑。了解了上面的概念,那我們所說的外部儲存訪問限制,可以認為是針對getExternalStorageDirectory()路徑下的文件。具體的規(guī)則如下表:上圖將外部存儲空間分為了三部分:特定目錄(App-specific),使用getExternalFilesDir()或getExternalCacheDir()方法訪問。無需權(quán)限,且卸載應用時會自動刪除。照片、視頻、音頻這類媒體文件。使用MediaStore訪問,訪問其他應用的媒體文件時需要READ_EXTERNAL_STORAGE權(quán)限。其他目錄,使用存儲訪問框架SAF(StorageAccessFramwork)所以在Android10上即使你擁有了儲存空間的讀寫權(quán)限,也無法保證可以正常的進行文件的讀寫操作。適配最簡單粗暴的方法就是在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true"來請求使用舊的存儲模式。但是我不推薦此方法。因為在下一個版本的Android中,此條配置將會失效,將強制采用外部儲存限制。其實早在AndroidQBeta3之前都是強制的,但為了給開發(fā)者適配的時間才沒有強制執(zhí)行。所以如果你不抓住這段時間去適配,那么今年下半年出了Android11。。。直接開花~~如果你已經(jīng)適配Android10,這里有個現(xiàn)象要注意一下:如果應用通過升級安裝,那么還會使用以前的儲存模式(LegacyView)。只有通過首次安裝或是卸載重新安裝才能啟用新模式(FilteredView)。所以在適配時,我們的判斷代碼如下:

//

使用Environment.isExternalStorageLegacy()來檢查APP的運行模式

if

(Build.VERSION.SDK_INT

>=

Build.VERSION_CODES.Q

&&

!Environment.isExternalStorageLegacy())

{

}這樣的好處是你可以在用戶升級后,能方便的將用戶的數(shù)據(jù)移動至應用的特定目錄。否則你只能通過SAF去移動,這樣會非常麻煩。如果你要移動數(shù)據(jù)注意只適用于Android10下,所以現(xiàn)在適配反而是一個好時機。當然如果你不需要遷移數(shù)據(jù),那適配會更省事。下面就說說推薦適配方案:對于應用中涉及的文件操作,修改一下你的文件路徑。以前我們習慣使用Environment.getExternalStorageDirectory()方法,那么現(xiàn)在可以使用getExternalFilesDir()方法(包括下載的安裝包這類的文件)。如果是緩存類型文件,可以放到getExternalCacheDir()路徑下?;蛘呤褂肕ediaStore,將文件存至對應的媒體類型中(圖片:MediaStore.Images,視頻:MediaStore.Video,音頻:MediaStore.Audio),不過僅限于多媒體文件。下面代碼將圖片保存到公共目錄下,返回Uri:public

static

Uri

createImageUri(Context

context)

{

ContentValues

values

=

new

ContentValues();

//

需要指定文件信息時,非必須

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,而應使用Uri。否則報錯如下:java.io.FileNotFoundException:

open

failed:

EACCES

(Permission

denied)比如我在適配項目中使用的圖片選擇器時,首先修改了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的邏輯(因為一般都是上傳File,沒有直接上傳Uri的操作),所以我將最終選擇的文件又轉(zhuǎ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];

}這樣下來,一個圖片選擇器就基本適配完了。補充應用在卸載后,會將App-specific目錄下的數(shù)據(jù)刪除,如果在AndroidManifest.xml中聲明:android:hasFragileUserData="true"用戶可以選擇是否保留。對于SAF的使用,可以查看我之前寫的SAF使用攻略,這里就不展開說了。最后這里有一個介紹ScopedStorage的視頻,推薦觀看:2.權(quán)限變化從6.0開始,基本每次都會有權(quán)限方面變動,這次也不例外。(前幾天發(fā)布了Android11的預覽版,看來也有權(quán)限方面的變化。。。單次權(quán)限即將到來)1.在后臺運行時訪問設備位置信息需要權(quán)限Android10引入了ACCESS_BACKGROUND_LOCATION權(quán)限(危險權(quán)限)。<uses-permission

android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>該權(quán)限允許應用程序在后臺訪問位置。如果請求此權(quán)限,則還必須請求ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION權(quán)限。只請求此權(quán)限無效果。在Android10的設備上,如果你的應用的targetSdkVersion<29,則在請求ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION權(quán)限時,系統(tǒng)會自動同時請求ACCESS_BACKGROUND_LOCATION。在請求彈框中,選擇“始終允許”表示同意后臺獲取位置信息,選擇“僅在應用使用過程中允許”或"拒絕"選項表示拒絕授權(quán)。如果你的應用的targetSdkVersion>=29,則請求ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION權(quán)限表示在前臺時擁有訪問設備位置信息的權(quán)。在請求彈框中,選擇“始終允許”表示前后臺都可以獲取位置信息,選擇“僅在應用使用過程中允許”只表示擁有前臺的權(quán)限。總結(jié)一下就是下圖:

其實官方不推薦你使用申請后臺訪問權(quán)的方式,因為這樣的結(jié)果無非就是多請求一個權(quán)限,那么這像變更還有什么意義?申請過多的權(quán)限,也會造成用戶的反感。所以官方推薦使用前臺服務來實現(xiàn),在前臺服務中獲取位置信息。首先在清單中對應的service中添加android:foregroundServiceType="location":

<service

android:name="MyNavigationService"

android:foregroundServiceType="location"

...

>

...

</service>啟動前臺服務前檢查是否具有前臺的訪問權(quán)限:

boolean

permissionApproved

=

ActivityCompat.checkSelfPermission(this,

Manifest.permission.ACCESS_COARSE_LOCATION)

==

PackageManager.PERMISSION_GRANTED;

if

(permissionApproved)

{

//

啟動前臺服務

}

else

{

//

請求前臺訪問位置權(quán)限

}如此一來就可以在Service中獲取位置信息。2.一些電話、藍牙和WLAN的API需要精確位置權(quán)限下面列舉了Android10中必須具有ACCESS_FINE_LOCATION權(quán)限才能使用類和方法:電話

TelephonyManagergetCellLocation()getAllCellInfo()requestNetworkScan()requestCellInfoUpdate()getAvailableNetworks()getServiceState()TelephonyScanManagerrequestNetworkScan()TelephonyScanManager.NetworkScanCallbackonResults()PhoneStateListeneronCellLocationChanged()onCellInfoChanged()onServiceStateChanged()WLAN

WifiManagerstartScan()getScanResults()getConnectionInfo()getConfiguredNetworks()WifiAwareManagerWifiP2pManagerWifiRttManager藍牙

BluetoothAdapterstartDiscovery()startLeScan()BluetoothAdapter.LeScanCallbackBluetoothLeScannerstartScan()我們可以根據(jù)上面提供的具體類和方法,在適配項目中檢查是否有使用到并及時處理。3.ACCESS_MEDIA_LOCATIONAndroid10新增權(quán)限,上面有提到,不贅述了。4.PROCESS_OUTGOING_CALLSAndroid10上該權(quán)限已廢棄。3.后臺啟動Activity的限制簡單解釋就是應用處于后臺時,無法啟動Activity。比如點開一個應用會進入啟動頁或者廣告頁,一般會有幾秒的延時再跳轉(zhuǎn)至首頁。如果這期間你退到后臺,那么你將無法看到跳轉(zhuǎn)過程。而在之前的版本中,會強制彈出頁面至前臺。既然是限制,那么肯定有不受限的情況,主要有以下幾點:應用具有可見窗口,例如前臺Activity。應用在前臺任務的返回棧中已有的Activity。應用在Recents上現(xiàn)有任務的返回棧中已有的Activity。Recents就是我們的任務管理列表。應用收到系統(tǒng)的PendingIntent通知。應用收到它應該在其中啟動界面的系統(tǒng)廣播。示例包括ACTION_NEW_OUTGOING_CALL和SECRET_CODE_ACTION。應用可在廣播發(fā)送幾秒鐘后啟動Activity。用戶已向應用授予SYSTEM_ALERT_WINDOW權(quán)限,或是在應用權(quán)限頁開啟后臺彈出頁面的開關(guān)。因為此項行為變更適用于在Android10上運行的所有應用,所以這一限制導致最明顯的問題就是點擊推送信息時,有些應用無法進行正常的跳轉(zhuǎn)(具體的實現(xiàn)問題導致)。所以針對這類問題,可以采取PendingIntent的方式,發(fā)送通知時使用setContentIntent方法。當然你也可以申請相應權(quán)限或者白名單:不過申請白名單這種方法受各種手機廠商所限,很麻煩。感覺還不如引導用戶手動開啟權(quán)限。。。對于全屏intent,注意設置最高優(yōu)先級和添加USE_FULL_SCREEN_INTENT權(quán)限,這是一個普通權(quán)限。比如微信來語音或者視頻通話時,彈出的接聽頁面就是使用這一功能。

<uses-permission

android:name="android.permission.USE_FULL_SCREEN_INTENT"/>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();注意:在部分手機上,直接設置setPriority無效(或者說以渠道優(yōu)先級為準)。所以需要創(chuàng)建通知渠道時將重要性設置為IMPORTANCE_HIGH。NotificationChannel

channel

=

new

NotificationChannel(channelId,

"xxx",

NotificationManager.IMPORTANCE_HIGH);后臺啟動Activity的限制的目的是為了減少對用戶操作的中斷。如果你有要彈出的頁面,推薦你先彈出通知,讓用戶自己選擇接下來的操作,而不是一股腦的強制彈出。(如果你的全屏intent都讓用戶反感,那他也可以關(guān)掉你的通知,不至于任你擺布。)4.深色主題Android10新增了一個系統(tǒng)級的深色主題(在系統(tǒng)設置中開啟)。雖然深色主題并不是強制適配項,但是它可以帶給用戶更好的體驗:可大幅減少耗電量。OLED屏幕中每個像素都是自主發(fā)光,所以在顯示深色元素時像素所消耗的電流更低,尤其在純黑顏色時像素點可以完全關(guān)閉來達到省電的效果。為弱視以及對強光敏感的用戶提高可視性。深色可以降低屏幕的整體視覺亮度,減少對眼睛的視覺壓力。讓所有人都可以在光線較暗的環(huán)境中更輕松地使用設備。適配方法有兩種:1.手動適配(資源替換)官方文檔中提到的繼承Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight的方法,但這只是將我們使用的各種View的默認樣式進行了適配,并不太適用于實際項目的適配。因為具體的項目中的View都按照設計的風格進行了重定義。其實適配的方法很簡單,類似屏幕適配、國際化的操作,并不需要繼承上面的主題。比如你要修改顏色,就在res下新建values-night目錄,創(chuàng)建對應的colors.xml文件。將具體要修改的色值定義在里面。圖標之類的也是一個思路,創(chuàng)建對應的drawable-night目錄。只要你之前的代碼不是硬編碼且代碼規(guī)范,那么適配起來還是很輕松。2.自動適配(ForceDark)Android10提供ForceDark功能。一如其名,此功能可讓開發(fā)者快速實現(xiàn)深色主題背景,而無需明確設置DayNight主題背景。如果您的應用采用淺色主題背景,則ForceDark會分析應用的每個視圖,并在相應視圖在屏幕上顯示之前,自動應用深色主題背景。有些開發(fā)者會混合使用ForceDark和本機實現(xiàn),以縮短實現(xiàn)深色主題背景所需的時間。應用必須選擇啟用ForceDark,方法是在其主題背景中設置android:forceDarkAllowed="true"。此屬性會在所有系統(tǒng)及AndroidX提供的淺色主題背景(例如Theme.Material.Light)上設置。使用ForceDark時,您應確保全面測試應用,并根據(jù)需要排除視圖。如果您的應用使用DarkTheme主題(例如Theme.Material),則系統(tǒng)不會應用ForceDark。同樣,如果應用的主題背景繼承自DayNight主題(例如Theme.AppCompat.DayNight),則系統(tǒng)不會應用ForceDark,因為會自動切換主題背景。您可以通過android:forceDarkAllowed布局屬性或setForceDarkAllowed(boolean)在特定視圖上控制ForceDark。上述內(nèi)容我直接照搬文檔的說明??偨Y(jié)一下,使用ForceDark需要注意幾點:如果使用的是DayNight或DarkTheme主題,則設置forceDarkAllowed不生效。如果有需要排除適配的部分,可以在對應的View上設置forceDarkAllowed為false。這里說說我實際使用此方法的感受:整體還是不錯的,設置的色值會自動取反。但也因此顏色不受控制,能否達到預期效果是個需要注意的問題。追求快速適配可以采取此方案。手動切換主題使用AppCompatDelegate.setDefaultNightMode(@NightModeintmode)方法,其中參數(shù)mode有以下幾種:淺色-MODE_NIGHT_NO深色-MODE_NIGHT_YES由省電模式設置-MODE_NIGHT_AUTO_BATTERY系統(tǒng)默認-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()方法,可以獲取到當前的模式,這樣便于代碼中去適配。監(jiān)聽深色主題是否開啟首先在清單文件中給對應的Activity配置android:configChanges="uiMode":

<activity

android:name=".MyActivity"

android:configChanges="uiMode"

/>這樣在onConfigurationChanged方法中就可以獲取: @Override

public

void

onConfigurationChanged(@NonNull

Configuration

newConfig)

{

super.onConfigurationChanged(newConfig);

int

currentNightMode

=

newConfig.uiMode

&

Configuration.UI_MODE_NIGHT_MASK;

switch

(currentNightMode)

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論