日韩精品亚洲精品中文字幕乱伦AV|曰韩区二区三区日韩中文字幕五码|ady99久久人人看人人摸人人|动漫一区二区黄99精品视频在线|AV片在线观看亚洲中文国产精品|伦乱在线VA欧美性爱一二区|亚洲无码mv91热色视频|激情六月综合欧美精品中文

當(dāng)前位置:首頁(yè) > 軟件開(kāi)放 > 正文內(nèi)容

全屏相冊(cè)代碼(全屏圖片代碼)

軟件開(kāi)放7個(gè)月前 (06-15)421

微信改了推動(dòng)機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào)

公眾號(hào)回復(fù)加入BATcoder技術(shù)群BAT

作者:fishforest

全屏相冊(cè)代碼(全屏圖片代碼)

鏈接:https://www.jianshu.com/p/d5573e312bb8

1、存儲(chǔ)基本知識(shí)

先來(lái)看看存儲(chǔ)區(qū)域劃分:

image.png

其中,以下目錄無(wú)需存儲(chǔ)權(quán)限即可訪問(wèn):

1、App自身的內(nèi)部存儲(chǔ)

2、App自身的自帶外部存儲(chǔ)-私有目錄

1、App自身的內(nèi)部存儲(chǔ)

2、App自身的自帶外部存儲(chǔ)-私有目錄

剩下的都需要申請(qǐng)存儲(chǔ)權(quán)限,Android 10.0前后對(duì)于存儲(chǔ)作用域訪問(wèn)的區(qū)別就體現(xiàn)在如何訪問(wèn)剩余這些目錄內(nèi)的文件。

重點(diǎn)在自帶外部存儲(chǔ)之共享存儲(chǔ)空間和其它目錄

2、Android 10.0 之前訪問(wèn)方式

繼續(xù)細(xì)分為Android 6.0 之前和之后。

Android 6.0 之前訪問(wèn)方式

展開(kāi)全文

Android 6.0 之前是無(wú)需申請(qǐng)動(dòng)態(tài)權(quán)限的,在AndroidManifest.xml 里聲明存儲(chǔ)權(quán)限:

就可以訪問(wèn)共享存儲(chǔ)空間、其它目錄下的文件了。

Android 6.0 之后的訪問(wèn)方式動(dòng)態(tài)申請(qǐng)權(quán)限

Android 6.0 后需要?jiǎng)討B(tài)申請(qǐng)權(quán)限,除了在AndroidManifest.xml 里聲明存儲(chǔ)權(quán)限外,還需要在代碼里動(dòng)態(tài)申請(qǐng)。

//申請(qǐng)權(quán)限privatevoidrequestPermission( Activity activity, String requestPermissionList[]) { ActivityCompat.requestPermissions(activity, requestPermissionList, 100); }

//用戶作出選擇后,返回申請(qǐng)的結(jié)果@ OverridepublicvoidonRequestPermissionsResult( intrequestCode, @NonNull String[] permissions, @NonNull int[] grantResults ) { if(requestCode == 100) { for( inti = 0; i permissions.length; i++) { if(permissions[i]. equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { if(grantResults[i] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(MainActivity. this, "存儲(chǔ)權(quán)限申請(qǐng)成功", Toast.LENGTH_SHORT).show; } else{ Toast.makeText(MainActivity. this, "存儲(chǔ)權(quán)限申請(qǐng)失敗", Toast.LENGTH_SHORT).show; }}}}}

//測(cè)試申請(qǐng)存儲(chǔ)權(quán)限privatevoidtestPermission( Activity activity) { String[] checkList = newString[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}; ListString needRequestList = checkPermission(activity, checkList);if(needRequestList.isEmpty) { Toast.makeText(MainActivity. this, "無(wú)需申請(qǐng)權(quán)限", Toast.LENGTH_SHORT).show; } else{ requestPermission(activity, needRequestList.toArray( newString[needRequestList.size])); }}

申請(qǐng)權(quán)限后,提示用戶作出選擇:

訪問(wèn)文件

權(quán)限申請(qǐng)成功后,即可對(duì)自帶外部存儲(chǔ)之共享存儲(chǔ)空間和其它目錄進(jìn)行訪問(wèn)。

分別以共享存儲(chǔ)空間和其它目錄為例,闡述訪問(wèn)方式:

訪問(wèn)共享存儲(chǔ)空間

共享存儲(chǔ)空間分為兩類文件:媒體文件和文檔/其它文件。

訪問(wèn)媒體文件

目的是拿到媒體文件的路徑,有兩種方式獲取路徑:

1、直接構(gòu)造路徑

以圖片為例,假設(shè)圖片存儲(chǔ)在/sdcard/Pictures/目錄下。

如上,myPic.png的路徑:/storage/emulated/0/Pictures/myPic.png,拿到路徑后就可以解析并獲取Bitmap。

2、通過(guò)MediaStore獲取路徑

沿用上篇的demo:

同樣的,也是拿到圖片路徑后獲取Bitmap。

還有一種不直接通過(guò)路徑訪問(wèn)的方法:

3、通過(guò)MediaStore獲取Uri

與直接拿到路徑不同的是,此處拿到的是Uri。圖片的信息封裝在Uri里,通過(guò)Uri構(gòu)造出InputStream,再進(jìn)行圖片解碼拿到Bitmap

訪問(wèn)文檔和其它文件

1、直接構(gòu)造路徑

與媒體文件一樣,可以直接構(gòu)造路徑訪問(wèn)。

2、通過(guò)SAF訪問(wèn)

Storage Access Framework 簡(jiǎn)稱SAF:存儲(chǔ)訪問(wèn)框架。相當(dāng)于系統(tǒng)內(nèi)置了文件選擇器,通過(guò)它可以拿到想要訪問(wèn)的文件信息。

同樣的以獲取圖片為例:

@OverrideprotectedvoidonActivityResult( intrequestCode, intresultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data);

if(requestCode == 100) { //選中返回的圖片封裝在uri里Uri uri = data.getData;openUri(uri);}}

privatevoidopenUri(Uri uri){ try{ //從uri構(gòu)造輸入流InputStream fis = getContentResolver.openInputStream(uri);Bitmap bitmap = BitmapFactory.decodeStream(fis);} catch(Exception e) {

}}

可以看出,通過(guò)SAF并不能直接拿到圖片的路徑,圖片的信息封裝在Uri里,通過(guò)Uri構(gòu)造出InputStream,再進(jìn)行圖片解碼拿到Bitmap

訪問(wèn)其它目錄

有兩種方式:

1、直接構(gòu)造路徑

在/sdcard/目錄下直接創(chuàng)建目錄:

可以看出,/sdcard/myDir/目錄創(chuàng)建成功。

2、通過(guò)SAF訪問(wèn)

與共享存儲(chǔ)空間SAF訪問(wèn)方式一致。

Android 10.0 之前訪問(wèn)方式總結(jié)

由上面分析的共享存儲(chǔ)空間/其它目錄訪問(wèn)方式可知,訪問(wèn)目錄/文件可通過(guò)如下兩個(gè)方法:

1、通過(guò)路徑訪問(wèn)。路徑可以直接構(gòu)造也可以通過(guò)MediaStore獲取。

2、通過(guò)Uri訪問(wèn)。Uri可以通過(guò)MediaStore或者SAF獲取。

1、通過(guò)路徑訪問(wèn)。路徑可以直接構(gòu)造也可以通過(guò)MediaStore獲取。

2、通過(guò)Uri訪問(wèn)。Uri可以通過(guò)MediaStore或者SAF獲取。

Android 6.0 以下訪問(wèn)共享存儲(chǔ)空間/其它目錄步驟:

1、AndroidManifest.xml里聲明存儲(chǔ)權(quán)限

2、通過(guò)路徑或者Uri訪問(wèn)文件

1、AndroidManifest.xml里聲明存儲(chǔ)權(quán)限

2、通過(guò)路徑或者Uri訪問(wèn)文件

Android 6.0(含)~Android 10.0(不含)訪問(wèn)共享存儲(chǔ)空間/其它目錄步驟:

1、AndroidManifest.xml里聲明存儲(chǔ)權(quán)限

2、動(dòng)態(tài)申請(qǐng)存儲(chǔ)權(quán)限

3、通過(guò)路徑或者Uri訪問(wèn)文件

1、AndroidManifest.xml里聲明存儲(chǔ)權(quán)限

2、動(dòng)態(tài)申請(qǐng)存儲(chǔ)權(quán)限

3、通過(guò)路徑或者Uri訪問(wèn)文件

3、Android 10.0 訪問(wèn)方式變更

為什么要變更

你可能已經(jīng)發(fā)現(xiàn)了上面訪問(wèn)方式的弊端,比如我們能夠直接在/sdcard/目錄下創(chuàng)建目錄/文件。事實(shí)上,很多App就是這么干的,看圖說(shuō)話:

可以看出/sdcard/目錄下,如淘寶、qq、qq瀏覽器、微博、支付寶等都自己建了目錄。

這么看來(lái),導(dǎo)致目錄結(jié)構(gòu)很亂,而且App卸載后,對(duì)應(yīng)的目錄并沒(méi)有刪除,于是就是遺留了很多"垃圾"文件,久而久之不處理,用戶的存儲(chǔ)空間越來(lái)越小。

總結(jié)弊端如下:

1、在設(shè)置里"Clear storage"或者"Clear cache"并不能刪除該目錄下的文件

2、卸載App也不能刪除該目錄下的文件

3、App可以隨意修改其它目錄下的文件,如修改別的App創(chuàng)建的文件等,不安全

1、在設(shè)置里"Clear storage"或者"Clear cache"并不能刪除該目錄下的文件

2、卸載App也不能刪除該目錄下的文件

3、App可以隨意修改其它目錄下的文件,如修改別的App創(chuàng)建的文件等,不安全

你也許會(huì)問(wèn),為什么要在/sdcard/目錄下新建自己的目錄呢?

大體有以下兩個(gè)原因:

1、此處新建的目錄不會(huì)被設(shè)置里的App存儲(chǔ)用量統(tǒng)計(jì),讓用戶"看起來(lái)"自己的App占用的存儲(chǔ)空間很小

2、方便操作文件

1、此處新建的目錄不會(huì)被設(shè)置里的App存儲(chǔ)用量統(tǒng)計(jì),讓用戶"看起來(lái)"自己的App占用的存儲(chǔ)空間很小

2、方便操作文件

面對(duì)眾多App不講"碼德"隨意新建目錄/文件的現(xiàn)象,Google在Android 10.0上重拳出擊了。

引入Scoped Storage

翻譯成中文有好幾個(gè)版本:作用域存儲(chǔ)、分區(qū)存儲(chǔ)、沙盒存儲(chǔ)。

具體中文翻譯不重要,下面以分區(qū)存儲(chǔ)指代。

分區(qū)存儲(chǔ)原理:

1、App訪問(wèn)自身內(nèi)部存儲(chǔ)空間、訪問(wèn)外部存儲(chǔ)空間-App私有目錄不需要任何權(quán)限(這個(gè)與Android 10.0之前一致)

2、外部存儲(chǔ)空間-共享存儲(chǔ)空間、外部存儲(chǔ)空間-其它目錄 App無(wú)法通過(guò)路徑直接訪問(wèn),不能新建、刪除、修改目錄/文件等

3、外部存儲(chǔ)空間-共享存儲(chǔ)空間、外部存儲(chǔ)空間-其它目錄 需要通過(guò)Uri訪問(wèn)

1、App訪問(wèn)自身內(nèi)部存儲(chǔ)空間、訪問(wèn)外部存儲(chǔ)空間-App私有目錄不需要任何權(quán)限(這個(gè)與Android 10.0之前一致)

2、外部存儲(chǔ)空間-共享存儲(chǔ)空間、外部存儲(chǔ)空間-其它目錄 App無(wú)法通過(guò)路徑直接訪問(wèn),不能新建、刪除、修改目錄/文件等

3、外部存儲(chǔ)空間-共享存儲(chǔ)空間、外部存儲(chǔ)空間-其它目錄 需要通過(guò)Uri訪問(wèn)

分區(qū)存儲(chǔ)的變更在于第二點(diǎn)、第三點(diǎn)。

為什么Uri能夠訪問(wèn)

先來(lái)看為什么通過(guò)路徑無(wú)法直接訪問(wèn)。

我們知道訪問(wèn)文件最終是通過(guò)構(gòu)造InputStream/OutputStream來(lái)實(shí)現(xiàn)的,以InputStream為例,看看其構(gòu)造方法:

可以看出,要想FileInputStream 能讀入文件,核心是需要構(gòu)造FileDeor,而對(duì)于Android 10.0,直接通過(guò)路徑構(gòu)造FileDeor 會(huì)拋出異常。

那么我們自然會(huì)想到,有沒(méi)有通過(guò)構(gòu)造好的FileDeor 來(lái)生成FileInputStream對(duì)象,進(jìn)而使用read(xx)方法讀取數(shù)據(jù)。

還真有,請(qǐng)看:通過(guò)Uri構(gòu)造InputStream。

進(jìn)入看其源碼:

AssetFileDeor 持有ParcelFileDeor 引用,而ParcelFileDeor 持有FileDeor 引用。

同理也適用于FileOutputStream。因此,通過(guò)Uri能夠訪問(wèn)文件。

4、如何不適配Android 10.0

從以上分析可知,適配Android 10.0 有點(diǎn)麻煩,問(wèn)題來(lái)了有沒(méi)有簡(jiǎn)單的方法繞過(guò)檢測(cè)。

第一種方法

1、Android 10.0 及其以后才會(huì)有分區(qū)存儲(chǔ)功能,只要Android 設(shè)備不升級(jí)系統(tǒng)到Android 10.0以后,就不會(huì)有問(wèn)題。

2、可能覺(jué)得這是句廢話,其實(shí)不然,有些定制的設(shè)備系統(tǒng)一般都不會(huì)升級(jí)的。

1、Android 10.0 及其以后才會(huì)有分區(qū)存儲(chǔ)功能,只要Android 設(shè)備不升級(jí)系統(tǒng)到Android 10.0以后,就不會(huì)有問(wèn)題。

2、可能覺(jué)得這是句廢話,其實(shí)不然,有些定制的設(shè)備系統(tǒng)一般都不會(huì)升級(jí)的。

如果不能使用第一種方法,還可以采用第二種方法。

第二種方法

1、Android 一般升級(jí)功能的時(shí)候都會(huì)配合targetSdkVersion使用。只要targetSdkVersion=28,分區(qū)存儲(chǔ)功能就不會(huì)開(kāi)啟。

1、Android 一般升級(jí)功能的時(shí)候都會(huì)配合targetSdkVersion使用。只要targetSdkVersion=28,分區(qū)存儲(chǔ)功能就不會(huì)開(kāi)啟。

有關(guān)targetSdkVersion 作用請(qǐng)移步:targetSdkVersion、compileSdkVersion、minSdkVersion作用與區(qū)別

如果第二種方法也不能使用,則還有第三種方法。

第三種方法

在AndroidManifest.xml 里application標(biāo)簽下添加:

android:requestLegacyExternalStorage="true" 可禁用分區(qū)存儲(chǔ)

在AndroidManifest.xml 里application標(biāo)簽下添加:

android:requestLegacyExternalStorage="true" 可禁用分區(qū)存儲(chǔ)

從長(zhǎng)遠(yuǎn)的角度看,以上三個(gè)方法都不是一勞永逸的方法,其中第二種、第三種方法是Google 留給App開(kāi)發(fā)者適配的緩沖時(shí)間。

對(duì)于第二種方法:

Google 在App上架App Store 時(shí)候可能會(huì)強(qiáng)制要求升級(jí)targetSdkVersion,因此該方法不保險(xiǎn)。

Google 在App上架App Store 時(shí)候可能會(huì)強(qiáng)制要求升級(jí)targetSdkVersion,因此該方法不保險(xiǎn)。

對(duì)于第三種方法:

在Android 11會(huì)忽略該字段,強(qiáng)制開(kāi)啟分區(qū)存儲(chǔ),該字段也不怎么靠譜。

在Android 11會(huì)忽略該字段,強(qiáng)制開(kāi)啟分區(qū)存儲(chǔ),該字段也不怎么靠譜。

因此,最終還是需要老老實(shí)實(shí)按照Google 的要求適配Android 10.0,下篇將重點(diǎn)分析Android 10.0/11 該如何來(lái)適配。

本文基于Android 10.0。

5、MediaStore 基本知識(shí)

上篇已經(jīng)分析得出結(jié)論,Android 10.0 存儲(chǔ)訪問(wèn)方式變更地方在于:

自帶外部存儲(chǔ)-共享存儲(chǔ)空間和自帶外部存儲(chǔ)-其它目錄

自帶外部存儲(chǔ)-共享存儲(chǔ)空間和自帶外部存儲(chǔ)-其它目錄

以上兩個(gè)地方不能通過(guò)路徑直接訪問(wèn)文件,而是需要通過(guò)Uri訪問(wèn)。

共享存儲(chǔ)空間

共享存儲(chǔ)空間存放的是圖片、視頻、音頻等文件,這些資源是公用的,所有App都能夠訪問(wèn)它們。

系統(tǒng)里有external.db數(shù)據(jù)庫(kù),該數(shù)據(jù)庫(kù)里有files表,該表里存放著共享文件的諸多信息,如圖片有寬高,經(jīng)緯度、存放路徑等,視頻寬高、時(shí)長(zhǎng)、存放路徑等。而文件真正存放的地方在于共享存儲(chǔ)空間。

1、保存圖片到相冊(cè)

當(dāng)App1保存圖片到相冊(cè)時(shí),簡(jiǎn)單流程如下:

1、將路徑信息寫(xiě)入數(shù)據(jù)庫(kù)里,并獲取Uri

2、通過(guò)Uri構(gòu)造輸出流

3、將該圖片保存在/sdcard/Pictures/目錄下

1、將路徑信息寫(xiě)入數(shù)據(jù)庫(kù)里,并獲取Uri

2、通過(guò)Uri構(gòu)造輸出流

3、將該圖片保存在/sdcard/Pictures/目錄下

2、從相冊(cè)獲取圖片

當(dāng)App2從相冊(cè)獲取圖片時(shí),簡(jiǎn)單流程如下:

1、先查詢數(shù)據(jù)庫(kù),找到對(duì)應(yīng)的圖片Cursor

2、從Cursor里構(gòu)造Uri

3、從Uri構(gòu)造輸入流讀取圖片

1、先查詢數(shù)據(jù)庫(kù),找到對(duì)應(yīng)的圖片Cursor

2、從Cursor里構(gòu)造Uri

3、從Uri構(gòu)造輸入流讀取圖片

以上以圖片為例簡(jiǎn)單分析了共享存儲(chǔ)空間文件的寫(xiě)入與讀取,實(shí)際上對(duì)于視頻、音頻步驟亦是如此。

MediaStore作用

共享存儲(chǔ)空間里存放著圖片、視頻、音頻、下載的文件,App獲取或者插入文件的時(shí)候怎么區(qū)分這些類型呢?

這個(gè)時(shí)候就需要MediaStore,來(lái)看看MediaStore.java

可以看出其內(nèi)部有Audio、Images等內(nèi)部類,這些內(nèi)部類里記錄著files表的各個(gè)字段名,通過(guò)構(gòu)造這些參數(shù)就可以插入相應(yīng)的字段值以及獲取對(duì)應(yīng)的字段值。

MediaStore 實(shí)際上就是相當(dāng)于給各個(gè)字段起了別名,我們編碼的時(shí)候更容易記住與使用:

MediaStore和Uri聯(lián)系

比如想要查詢共享存儲(chǔ)空間里的圖片文件:

MediaStore.Images.Media.EXTERNAL_CONTENT_URI 意思是指定查詢文件的類型是圖片,并構(gòu)造成Uri對(duì)象,Uri實(shí)現(xiàn)了Parcelable,能夠在進(jìn)程間傳遞。

接收方(另一個(gè)進(jìn)程收到后),匹配Uri,解析出對(duì)應(yīng)的字段,進(jìn)行具體的操作。

當(dāng)然,MediaStore是系統(tǒng)提供的方便操作共享存儲(chǔ)空間的類,若是自己寫(xiě)ContentProvider,則也可以自定義類似MediaStore的類用來(lái)標(biāo)記自己的數(shù)據(jù)庫(kù)表的字段。

6、通過(guò)Uri讀取和寫(xiě)入文件

既然不能通過(guò)路徑直接訪問(wèn)文件,那么來(lái)看看如何通過(guò)Uri訪問(wèn)文件。在上篇文章里提到過(guò): Uri可以通過(guò)MediaStore或者SAF獲取。(此處需要注意的是:雖然也可以通過(guò)文件路徑直接構(gòu)造Uri,但是此種方式構(gòu)造的Uri是沒(méi)有權(quán)限訪問(wèn)文件的)

先來(lái)看看通過(guò)SAF獲取Uri。

從Uri讀取文件

現(xiàn)在/sdcard/目錄下存在一個(gè)文件名為:mytest.txt。

該文件內(nèi)容是:

傳統(tǒng)的直接讀取mytest.txt方法:

try{ File file = newFile(filePath); FileInputStream fileInputStream = newFileInputStream(file); BufferedInputStream bis = newBufferedInputStream(fileInputStream); byte[] readContent = newbyte[ 1024]; intreadLen = 0; while(readLen != -1) { readLen = bis.read(readContent, 0, readContent.length); if(readLen 0) { String content = newString(readContent); Log.d( "test", "read content:"+ content.substring( 0, readLen)); }}fileInputStream.close;} catch(Exception e) {

}}

開(kāi)啟分區(qū)存儲(chǔ)功能后,這種方法是不可取的,會(huì)報(bào)權(quán)限錯(cuò)誤。

而mytest.txt不屬于共享存儲(chǔ)空間的文件,是屬于其它目錄的,因此不能通過(guò)MediaStore獲取,只能通過(guò)SAF獲取,如下:

@OverrideprotectedvoidonActivityResult( intrequestCode, intresultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data);

if(requestCode == 100) { //選中返回的文件信息封裝在Uri里Uri uri = data.getData;openUriForRead(uri);}}

拿到Uri后,用來(lái)構(gòu)造輸入流讀取文件。

try{ //獲取輸入流InputStream inputStream = getContentResolver.openInputStream(uri);byte[] readContent = newbyte[ 1024]; intlen = 0; do{ //讀文件len = inputStream.read(readContent);if(len != -1) { Log.d( "test", "read content:"+ newString(readContent).substring( 0, len)); }} while(len != -1); inputStream.close;} catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }}

最終輸出:

由此可以看出,mytest.txt屬于"其它目錄"下的文件,因此需要通過(guò)SAF訪問(wèn),SAF返回Uri,通過(guò)Uri構(gòu)造InputStream即可讀取文件。

從Uri寫(xiě)入文件

繼續(xù)來(lái)看看寫(xiě)的過(guò)程,現(xiàn)在需要往mytest.txt寫(xiě)入內(nèi)容。

同樣的,還是需要通過(guò)SAF拿到Uri,拿到Uri后構(gòu)造輸出流:

try{ //從uri構(gòu)造輸出流OutputStream outputStream = getContentResolver.openOutputStream(uri);//待寫(xiě)入的內(nèi)容String content = "hello world I'm from SAF\n"; //寫(xiě)入文件outputStream.write(content.getBytes);outputStream.flush;outputStream.close;} catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }}

最后來(lái)看看文件是否寫(xiě)入成功,通過(guò)SAF再次讀取mytest.txt,發(fā)現(xiàn)正好是之前寫(xiě)入的內(nèi)容,說(shuō)明寫(xiě)入成功。

7、通過(guò)Uri 獲取圖片和插入相冊(cè)

上面列舉出了其它目錄下文件的讀寫(xiě),方法是通過(guò)SAF拿到Uri。

SAF好處是:

系統(tǒng)提供了文件選擇器,調(diào)用者只需要指定想要讀寫(xiě)的文件類型,比如文本類型、圖片類型、視頻類型等,選擇器就會(huì)過(guò)濾出相應(yīng)文件以供選擇。接入方便,選擇簡(jiǎn)單。

系統(tǒng)提供了文件選擇器,調(diào)用者只需要指定想要讀寫(xiě)的文件類型,比如文本類型、圖片類型、視頻類型等,選擇器就會(huì)過(guò)濾出相應(yīng)文件以供選擇。接入方便,選擇簡(jiǎn)單。

想想另一種場(chǎng)景:

想要自己實(shí)現(xiàn)相冊(cè)選擇器,那么就需要獲得共享存儲(chǔ)空間下的文件信息。此種場(chǎng)景下使用SAF是無(wú)法做到的。

想要自己實(shí)現(xiàn)相冊(cè)選擇器,那么就需要獲得共享存儲(chǔ)空間下的文件信息。此種場(chǎng)景下使用SAF是無(wú)法做到的。

因此問(wèn)題的關(guān)鍵是: 如何批量獲得共享存儲(chǔ)空間下圖片/視頻的信息?

答案是:ContentResolver+ContentProvider+MediaStore(ContentProvider對(duì)于調(diào)用者是透明的)。

以圖片為例,分析插入與查詢方式。

插入相冊(cè)

來(lái)看看圖片的插入過(guò)程:

ContentValues contentValues = newContentValues; contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName);if(Build.VERSION.SDK_INT = Build.VERSION_CODES.Q) { //RELATIVE_PATH 字段表示相對(duì)路徑--------(1)contentValues.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);} else{ StringdstPath = Environment.getExternalStorageDirectory + File.separator + Environment.DIRECTORY_PICTURES + File.separator + fileName;//DATA字段在Android 10.0 之后已經(jīng)廢棄contentValues.put(MediaStore.Images.ImageColumns.DATA, dstPath);}

//插入相冊(cè)-------(2)Uri uri = getContentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

//寫(xiě)入文件-------(3)write2File(uri, inputStream);}

重點(diǎn)說(shuō)明三個(gè)點(diǎn):

(1)

Android 10.0之前,MediaStore.Images.ImageColumns.DATA 字段記錄的是圖片的絕對(duì)路徑,而Android 10.0(含)之后,DATA 被廢棄,取而代之的是使用MediaStore.Images.ImageColumns.RELATIVE_PATH,表示相對(duì)路徑。比如指定RELATIVE_PATH為Environment.DIRECTORY_PICTURES,表示之后的圖片將會(huì)放到Environment.DIRECTORY_PICTURES目錄下。

(2)

調(diào)用ContentResolver里的方法插入相冊(cè)。

MediaStore.Images.Media.EXTERNAL_CONTENT_URI 指的是插入圖片表。

ContentValues 以Map的形式記錄了待寫(xiě)入的字段值。

插入后返回Uri。

(3)

以上兩步僅僅只是往數(shù)據(jù)庫(kù)里增加一條記錄,該記錄指向的新文件是空的,需要將圖片寫(xiě)入到新文件。

而新文件位于/sdcard/Pictures/目錄下,該目錄是不能直接通過(guò)路徑訪問(wèn)的,因此需要通過(guò)第二步返回的Uri進(jìn)行訪問(wèn)。

try{ //從Uri構(gòu)造輸出流OutputStream outputStream = getContentResolver.openOutputStream(uri);

byte[] in= newbyte[ 1024]; intlen = 0;

do{ //從輸入流里讀取數(shù)據(jù)len = inputStream.read( in); if(len != -1) { outputStream.write( in, 0, len); outputStream.flush;}} while(len != -1);

inputStream.close;outputStream.close;

} catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }}

可以看出,目標(biāo)文件關(guān)聯(lián)的Uri有了,還需要原始的輸入文件。

測(cè)試上述的插入方法:

String picName = "mypic.jpg"; try{ File externalFilesDir = getExternalFilesDir( null); File file = newFile(externalFilesDir, picName); FileInputStream fis = newFileInputStream(file); insert2Album(fis, picName);} catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }}

其中,原始文件(圖片)存放于自帶外部存儲(chǔ)-App私有目錄,如下:

需要注意的是:

1、讀取原始文件需要權(quán)限,上述例子里的原始文件存放在自帶外部存儲(chǔ)-App私有目錄,因此本App可以使用路徑直接讀取

2、對(duì)于其他目錄則依然需要構(gòu)造Uri讀取,如通過(guò)SAF獲取Uri

1、讀取原始文件需要權(quán)限,上述例子里的原始文件存放在自帶外部存儲(chǔ)-App私有目錄,因此本App可以使用路徑直接讀取

2、對(duì)于其他目錄則依然需要構(gòu)造Uri讀取,如通過(guò)SAF獲取Uri

同樣的,想要從系統(tǒng)相冊(cè)中獲取圖片,也需要通過(guò)Uri訪問(wèn)。

if(cursor != null) { while(cursor.moveToNext) { //獲取唯一的idlongid = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)); //通過(guò)id構(gòu)造UriUri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);//解析uridecodeUriForBitmap(uri);}}}

privatevoiddecodeUriForBitmap( Uri uri) { if(uri == null) return;

try{ //構(gòu)造輸入流InputStream inputStream = getContentResolver.openInputStream(uri);//解析BitmapBitmap bitmap = BitmapFactory.decodeStream(inputStream);if(bitmap != null) Log.d( "test", "bitmap width-width:"+ bitmap.getWidth + "-"+ bitmap.getHeight); } catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }}

與插入相冊(cè)過(guò)程類似,同樣需要拿到Uri,再構(gòu)造輸入流,從輸入流讀取文件(圖片內(nèi)容)。

以上,通過(guò)Uri 獲取圖片和插入相冊(cè)分析完畢,共享存儲(chǔ)空間的其他文件類型如視頻、音頻、下載文件也是同樣的流程。

需要說(shuō)明的是上述的ContentResolver .insert(xx)/ContentResolver.query(xx) 的參數(shù)取值還可以更豐富,但不是本篇重點(diǎn),因此忽略了,實(shí)際使用過(guò)程中具體情況具體分析。

8、Android 11.0 權(quán)限申請(qǐng)

通過(guò)Uri訪問(wèn)文件似乎已經(jīng)滿足了Android 10.0適配要求,但是仔細(xì)想想還是有不足之處:

1、共享存儲(chǔ)空間只能通過(guò)MediaStore訪問(wèn),以前流行的訪問(wèn)方式是直接通過(guò)路徑訪問(wèn)。比如自己做的相冊(cè)管理器,先遍歷相冊(cè)拿到圖片/視頻的路徑,然后再解析成Bitmap展示,現(xiàn)在需要先拿到Uri,再解析成Bitmap,多少有些不方便。此外,也許你依賴的第三方庫(kù)是直接通過(guò)路徑訪問(wèn)文件的,而三方庫(kù)又沒(méi)有及時(shí)更新適配分區(qū)存儲(chǔ),可能就會(huì)導(dǎo)致用不了相應(yīng)的功能。

2、SAF雖然能夠訪問(wèn)其它目錄的文件,但是每次都需要跳轉(zhuǎn)到新的頁(yè)面去選擇,當(dāng)想要批量展示文件的時(shí)候,比如自己做的文件管理器,就需要列出當(dāng)前目錄下有哪些目錄/文件,這個(gè)時(shí)候需要有權(quán)限遍歷/sdcard/目錄。顯然,SAF并不能勝任此工作。

1、共享存儲(chǔ)空間只能通過(guò)MediaStore訪問(wèn),以前流行的訪問(wèn)方式是直接通過(guò)路徑訪問(wèn)。比如自己做的相冊(cè)管理器,先遍歷相冊(cè)拿到圖片/視頻的路徑,然后再解析成Bitmap展示,現(xiàn)在需要先拿到Uri,再解析成Bitmap,多少有些不方便。此外,也許你依賴的第三方庫(kù)是直接通過(guò)路徑訪問(wèn)文件的,而三方庫(kù)又沒(méi)有及時(shí)更新適配分區(qū)存儲(chǔ),可能就會(huì)導(dǎo)致用不了相應(yīng)的功能。

2、SAF雖然能夠訪問(wèn)其它目錄的文件,但是每次都需要跳轉(zhuǎn)到新的頁(yè)面去選擇,當(dāng)想要批量展示文件的時(shí)候,比如自己做的文件管理器,就需要列出當(dāng)前目錄下有哪些目錄/文件,這個(gè)時(shí)候需要有權(quán)限遍歷/sdcard/目錄。顯然,SAF并不能勝任此工作。

Android 11.0考慮到上面的問(wèn)題,因此做了新的優(yōu)化。

共享存儲(chǔ)空間-媒體文件訪問(wèn)變更

媒體文件可以通過(guò)路徑直接訪問(wèn):

try{ //取出路徑String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));Bitmap bitmap = BitmapFactory.decodeFile(path);} catch(Exception e) { Log.d( "test", e.getLocalizedMessage); }break; }}

可以看出,之前在Android 10.0上被禁用的訪問(wèn)方式,在Android 11.0上又被允許了,這就解決了上面的第一個(gè)問(wèn)題。

需要注意的是:此種方式只允許讀文件,寫(xiě)文件依然不行

Google 官方指導(dǎo)意見(jiàn)是:

雖然可以通過(guò)路徑直接訪問(wèn)媒體文件,但是這些操作最終是被重定向到MediaStore API的,重定向過(guò)程可能會(huì)損耗一些性能,并且直接通過(guò)路徑訪問(wèn)不一定比MediaStore API 訪問(wèn)快。

總之建議非必要的話不要直接使用路徑訪問(wèn)。

雖然可以通過(guò)路徑直接訪問(wèn)媒體文件,但是這些操作最終是被重定向到MediaStore API的,重定向過(guò)程可能會(huì)損耗一些性能,并且直接通過(guò)路徑訪問(wèn)不一定比MediaStore API 訪問(wèn)快。

總之建議非必要的話不要直接使用路徑訪問(wèn)。

假若App開(kāi)啟了分區(qū)存儲(chǔ)功能,當(dāng)App運(yùn)行在Android 10.0的設(shè)備上時(shí),是沒(méi)法遍歷/sdcard/目錄的。而在Android 11.0上運(yùn)行時(shí)是可以遍歷的,需要進(jìn)行如下幾個(gè)步驟。

1、聲明管理權(quán)限

在AndroidManifest.xml添加權(quán)限聲明

2、動(dòng)態(tài)申請(qǐng)所有文件訪問(wèn)權(quán)限

@OverrideprotectedvoidonActivityResult( intrequestCode, intresultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); //申請(qǐng)權(quán)限結(jié)果if(requestCode == 101) { if(Environment.isExternalStorageManager) { Toast.makeText(MainActivity. this, "訪問(wèn)所有文件權(quán)限申請(qǐng)成功", Toast.LENGTH_SHORT).show;

//遍歷目錄showAllFiles;}}}

此處申請(qǐng)權(quán)限不是以對(duì)話框的形式提示用戶,而是跳轉(zhuǎn)到新的頁(yè)面,說(shuō)明該權(quán)限的管理更嚴(yán)格。

3、遍歷目錄、讀寫(xiě)文件

擁有權(quán)限后,就可以進(jìn)行相應(yīng)的操作了。

文件管理器效果圖類似如下:

當(dāng)然讀寫(xiě)文件也不在話下了,比如往/sdcard/目錄下寫(xiě)入文件:

ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION 這個(gè)權(quán)限的名字看起來(lái)很唬人,感覺(jué)就像是能夠操作所有文件的樣子,這不就是打破了分區(qū)存儲(chǔ)的規(guī)則了嗎?其實(shí)不然:

即使擁有了該權(quán)限,依然不能訪問(wèn)內(nèi)部存儲(chǔ)和外部存儲(chǔ)-App私有目錄

即使擁有了該權(quán)限,依然不能訪問(wèn)內(nèi)部存儲(chǔ)和外部存儲(chǔ)-App私有目錄

需要說(shuō)明的是:

1、Environment.isExternalStorageManager、Build.VERSION_CODES.R 等需要編譯版本=30才能編譯通過(guò)。

2、Google 提示當(dāng)使用MANAGE_EXTERNAL_STORAGE 申請(qǐng)權(quán)限時(shí),并且targetSdkVersion=30,此種情況下App被禁止上架Google Play的,限制時(shí)間最早到2021年。因此,在此時(shí)間之前若是申請(qǐng)了MANAGE_EXTERNAL_STORAGE權(quán)限,最好不要升級(jí)targetSdkVersion到30以上。

1、Environment.isExternalStorageManager、Build.VERSION_CODES.R 等需要編譯版本=30才能編譯通過(guò)。

2、Google 提示當(dāng)使用MANAGE_EXTERNAL_STORAGE 申請(qǐng)權(quán)限時(shí),并且targetSdkVersion=30,此種情況下App被禁止上架Google Play的,限制時(shí)間最早到2021年。因此,在此時(shí)間之前若是申請(qǐng)了MANAGE_EXTERNAL_STORAGE權(quán)限,最好不要升級(jí)targetSdkVersion到30以上。

9、Android 10/11 存儲(chǔ)適配建議

好了,通過(guò)分析Android 10/11存儲(chǔ)適配方式,了解到了不同的系統(tǒng)需要如何進(jìn)行適配,此時(shí)就需要一個(gè)統(tǒng)一的適配方案了。

適配核心

分區(qū)存儲(chǔ)是核心,App自身產(chǎn)生的文件應(yīng)該存放在自己的目錄下:

/sdcard/Android/data/packagename/ 和/data/data/packagename/

/sdcard/Android/data/packagename/ 和/data/data/packagename/

這兩個(gè)目錄本App無(wú)需申請(qǐng)?jiān)L問(wèn)權(quán)限即可申請(qǐng),其它App無(wú)法訪問(wèn)本App的目錄。

適配共享存儲(chǔ)

共享存儲(chǔ)空間里的文件需要通過(guò)Uri構(gòu)造輸入輸出流訪問(wèn),Uri獲取方式有兩種:MediaStore和SAF。

適配其它目錄

在Android 11上需要申請(qǐng)?jiān)L問(wèn)所有文件的權(quán)限。

具體做法

第一步

在AndroidManifest.xml里添加如下字段:

權(quán)限聲明:

在application/標(biāo)簽下添加如下字段:

第二步

如果需要訪問(wèn)共享存儲(chǔ)空間,則判斷運(yùn)行設(shè)備版本是否大于等于Android6.0,若是則需要申請(qǐng)WRITE_EXTERNAL_STORAGE 權(quán)限。拿到權(quán)限后,通過(guò)Uri訪問(wèn)共享存儲(chǔ)空間里的文件。

如果需要訪問(wèn)其它目錄,則通過(guò)SAF訪問(wèn)

第三步

如果想要做文件管理器、病毒掃描管理器等功能。則判斷運(yùn)行設(shè)備版本是否大于等于Android 6.0,若是先需要申請(qǐng)普通的存儲(chǔ)權(quán)。若運(yùn)行設(shè)備版本為Android 10.0,則可以直接通過(guò)路徑訪問(wèn)/sdcard/目錄下文件(因?yàn)榻昧朔謪^(qū)存儲(chǔ));若運(yùn)行設(shè)備版本為Android 11.0,則需要申請(qǐng)MANAGE_EXTERNAL_STORAGE 權(quán)限。

以上是Android 存儲(chǔ)權(quán)限適配的全部?jī)?nèi)容。

本篇基于Android 10.0 11.0 。Android 10.0真機(jī)、Android 11.0模擬器

測(cè)試代碼(https://github.com/fishforest/AndroidDemo/tree/main/app/src/main/java/com/example/androiddemo/storagepermission)

? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

? 『BATcoder』做了多年安卓還沒(méi)編譯過(guò)源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

? 『BATcoder』我去!安裝Ubuntu還有坑?

? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

BATcoder技術(shù)群,讓一部分人先進(jìn)大廠

大家好,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷書(shū),連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。

想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)BAT 即可。

為了防止失聯(lián),歡迎關(guān)注我的小號(hào)

微信改了推送機(jī)制,真愛(ài)請(qǐng)星標(biāo)本公號(hào)??

掃描二維碼推送至手機(jī)訪問(wèn)。

版權(quán)聲明:本文由飛速云SEO網(wǎng)絡(luò)優(yōu)化推廣發(fā)布,如需轉(zhuǎn)載請(qǐng)注明出處。

本文鏈接:http://www.atlasseeker.com/post/116927.html

標(biāo)簽: 全屏相冊(cè)代碼

“全屏相冊(cè)代碼(全屏圖片代碼)” 的相關(guān)文章

軟件開(kāi)發(fā)工程師培訓(xùn)(軟件開(kāi)發(fā)工程師培訓(xùn)機(jī)構(gòu))

軟件開(kāi)發(fā)工程師培訓(xùn)(軟件開(kāi)發(fā)工程師培訓(xùn)機(jī)構(gòu))

今天給各位分享軟件開(kāi)發(fā)工程師培訓(xùn)的知識(shí),其中也會(huì)對(duì)軟件開(kāi)發(fā)工程師培訓(xùn)機(jī)構(gòu)進(jìn)行解釋,如果能碰巧解決你現(xiàn)在面臨的問(wèn)題,別忘了關(guān)注本站,現(xiàn)在開(kāi)始吧!本文目錄一覽: 1、北大青鳥(niǎo)java培訓(xùn):軟件開(kāi)發(fā)工程師如何培養(yǎng)思維能力? 2、洛陽(yáng)哪家軟件開(kāi)發(fā)培訓(xùn)學(xué)校比較好? 3、軟件工程師培訓(xùn)都有什么課程?哪有...

手機(jī)軟件開(kāi)發(fā)教程(安卓手機(jī)軟件開(kāi)發(fā)教程)

手機(jī)軟件開(kāi)發(fā)教程(安卓手機(jī)軟件開(kāi)發(fā)教程)

今天給各位分享手機(jī)軟件開(kāi)發(fā)教程的知識(shí),其中也會(huì)對(duì)安卓手機(jī)軟件開(kāi)發(fā)教程進(jìn)行解釋,如果能碰巧解決你現(xiàn)在面臨的問(wèn)題,別忘了關(guān)注本站,現(xiàn)在開(kāi)始吧!本文目錄一覽: 1、如何開(kāi)發(fā)手機(jī)app 2、app是如何制作的,APP如何開(kāi)發(fā)? 3、ios端的手機(jī)app開(kāi)發(fā)要怎么做? 4、如何制作軟件app...

泉州軟件開(kāi)發(fā)(泉州軟件開(kāi)發(fā)哪家好)

泉州軟件開(kāi)發(fā)(泉州軟件開(kāi)發(fā)哪家好)

本篇文章給大家談?wù)勅蒈浖_(kāi)發(fā),以及泉州軟件開(kāi)發(fā)哪家好對(duì)應(yīng)的知識(shí)點(diǎn),希望對(duì)各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、泉州中南軟件技術(shù)有限公司怎么樣? 2、泉州軟件公司排名? 3、請(qǐng)問(wèn)泉州比較有名的軟件公司有哪些 4、泉州南威軟件股份有限公司有雙休日嗎 5、福建省泉州市有...

手機(jī)百度瀏覽器收藏刪除了怎么恢復(fù)(手機(jī)百度刪除的收藏怎么恢復(fù))

手機(jī)百度瀏覽器收藏刪除了怎么恢復(fù)(手機(jī)百度刪除的收藏怎么恢復(fù))

今天給各位分享手機(jī)百度瀏覽器收藏刪除了怎么恢復(fù)的知識(shí),其中也會(huì)對(duì)手機(jī)百度刪除的收藏怎么恢復(fù)進(jìn)行解釋,如果能碰巧解決你現(xiàn)在面臨的問(wèn)題,別忘了關(guān)注本站,現(xiàn)在開(kāi)始吧!本文目錄一覽: 1、百度瀏覽器收藏誤刪怎么找回 2、手機(jī)百度瀏覽記錄怎么恢復(fù) 3、手機(jī)瀏覽器刪掉怎么找回收藏的網(wǎng)址 4、手機(jī)百度...

易語(yǔ)言反編譯工具(易語(yǔ)言反編譯器)

易語(yǔ)言反編譯工具(易語(yǔ)言反編譯器)

本篇文章給大家談?wù)勔渍Z(yǔ)言反編譯工具,以及易語(yǔ)言反編譯器對(duì)應(yīng)的知識(shí)點(diǎn),希望對(duì)各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、易語(yǔ)言怎么將一個(gè)文件寫(xiě)入exe文件(文件是易語(yǔ)言編譯的)里且寫(xiě)完后exe文件可以繼續(xù)運(yùn)行 2、怎樣把DLL反編譯成易語(yǔ)言源碼 3、.net,java都能被反編譯...

uu8686游戲交易平臺(tái)怎么樣(uu868游戲交易官網(wǎng)客服)

uu8686游戲交易平臺(tái)怎么樣(uu868游戲交易官網(wǎng)客服)

今天給各位分享uu8686游戲交易平臺(tái)怎么樣的知識(shí),其中也會(huì)對(duì)uu868游戲交易官網(wǎng)客服進(jìn)行解釋,如果能碰巧解決你現(xiàn)在面臨的問(wèn)題,別忘了關(guān)注本站,現(xiàn)在開(kāi)始吧!本文目錄一覽: 1、uu8968和8686購(gòu)寶通這兩個(gè)游戲交易平臺(tái)可靠嗎?可不可安全購(gòu)買? 2、有個(gè)叫UU86的交易平臺(tái)是騙人的 大家不要...