Android 5.0 (Lollipop) 용으로 제시된 새로운 SD 카드 액세스 API를 사용하는 방법은 무엇입니까?
배경
에 안드로이드 4.4 (킷캣), 구글은 매우 제한 SD 카드에 대한 액세스를했다.
Android Lollipop (5.0)부터 개발자는 이 Google 그룹 게시물 에 작성된대로 사용자에게 특정 폴더에 대한 액세스를 허용하도록 요청하는 새로운 API를 사용할 수 있습니다 .
문제
이 게시물은 두 웹 사이트를 방문하도록 지시합니다.
이것은 내부 예제처럼 보이지만 (나중에 API 데모에 표시 될 수 있음), 진행 상황을 이해하기는 매우 어렵습니다.
이 문서는 새로운 API의 공식 문서이지만 사용 방법에 대한 자세한 정보는 제공하지 않습니다.
다음과 같이 알려줍니다.
문서의 전체 하위 트리에 대한 전체 액세스 권한이 필요한 경우 ACTION_OPEN_DOCUMENT_TREE를 시작하여 사용자가 디렉토리를 선택할 수있게하십시오. 그런 다음 결과 getData ()를 fromTreeUri (Context, Uri)로 전달하여 사용자가 선택한 트리 작업을 시작하십시오.
DocumentFile 인스턴스 트리를 탐색 할 때 항상 getUri ()를 사용하여 해당 객체의 기본 문서를 나타내는 Uri를 얻을 수 있으며 openInputStream (Uri) 등에 사용할 수 있습니다.
KITKAT 또는 이전 버전을 실행하는 장치에서 코드를 단순화하기 위해 DocumentsProvider의 동작을 에뮬레이트하는 fromFile (File)을 사용할 수 있습니다.
질문
새 API에 대한 몇 가지 질문이 있습니다.
- 실제로 어떻게 사용합니까?
- 게시물에 따르면 OS는 앱에 파일 / 폴더에 액세스 할 수있는 권한이 부여되었음을 기억합니다. 파일 / 폴더에 액세스 할 수 있는지 어떻게 확인합니까? 액세스 할 수있는 파일 / 폴더 목록을 반환하는 기능이 있습니까?
- Kitkat에서이 문제를 어떻게 처리합니까? 지원 라이브러리의 일부입니까?
- OS에 어떤 파일 / 폴더에 액세스 할 수있는 앱을 표시하는 설정 화면이 있습니까?
- 동일한 기기에서 여러 사용자에게 앱을 설치하면 어떻게 되나요?
- 이 새로운 API에 대한 다른 문서 / 자습서가 있습니까?
- 권한을 취소 할 수 있습니까? 그렇다면 앱으로 전송하려는 의도가 있습니까?
- 선택한 폴더에서 권한 작업을 재귀 적으로 요청 하시겠습니까?
- 권한을 사용하면 사용자가 선택한 항목을 여러 개 선택할 수있는 기회가 제공됩니까? 또는 앱이 허용 할 파일 / 폴더를 의도적으로 구체적으로 알려야합니까?
- 에뮬레이터에서 새로운 API를 시도하는 방법이 있습니까? SD 카드 파티션이 있지만 기본 외부 저장소로 작동하므로 모든 권한이 이미 부여되어 있습니다 (단순 권한 사용).
- 사용자가 SD 카드를 다른 카드로 교체하면 어떻게됩니까?
좋은 질문이 많이 있습니다.
어떻게 사용합니까?
KitKat의 Storage Access Framework와 상호 작용하기위한 훌륭한 자습서는 다음과 같습니다.
https://developer.android.com/guide/topics/providers/document-provider.html#client
Lollipop의 새로운 API와 상호 작용하는 것은 매우 유사합니다. 사용자에게 디렉토리 트리를 선택하도록 프롬프트하려면 다음과 같은 의도를 시작할 수 있습니다.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 42);
그런 다음 onActivityResult ()에서 사용자가 선택한 Uri를 새 DocumentFile 도우미 클래스로 전달할 수 있습니다. 다음은 선택된 디렉토리의 파일을 나열한 다음 새 파일을 작성하는 빠른 예입니다.
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode == RESULT_OK) {
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
// List all existing files inside picked directory
for (DocumentFile file : pickedDir.listFiles()) {
Log.d(TAG, "Found file " + file.getName() + " with size " + file.length());
}
// Create a new file and write into it
DocumentFile newFile = pickedDir.createFile("text/plain", "My Novel");
OutputStream out = getContentResolver().openOutputStream(newFile.getUri());
out.write("A long time ago...".getBytes());
out.close();
}
}
에 의해 반환 된 Uri DocumentFile.getUri()
는 다른 플랫폼 API와 함께 사용하기에 충분히 유연합니다. 예를 들어, 사용하여 공유 할 수 Intent.setData()
와 함께 Intent.FLAG_GRANT_READ_URI_PERMISSION
.
당신은 열린 우리당이 네이티브 코드에서, 당신이 호출 할 수있는 액세스하려면 ContentResolver.openFileDescriptor()
다음 사용 ParcelFileDescriptor.getFd()
또는 detachFd()
전통적인 POSIX 파일 기술자의 정수를 얻을.
파일 / 폴더에 액세스 할 수 있는지 어떻게 확인합니까?
기본적으로 Storage Access Frameworks 의도를 통해 리턴 된 Uris는 재부팅시 지속 되지 않습니다 . 플랫폼은 권한을 유지하는 기능을 "제공"하지만 권한을 원할 경우 여전히 "권한을 가져와야"합니다. 위의 예에서는 다음과 같이 전화하십시오.
getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
앱이 ContentResolver.getPersistedUriPermissions()
API 를 통해 액세스 할 수있는 지속되는 보조금을 항상 파악할 수 있습니다 . 더 이상 지속 된 Uri에 액세스 할 필요가 없으면을 (를) 사용하여 해제 할 수 있습니다 ContentResolver.releasePersistableUriPermission()
.
KitKat에서 사용할 수 있습니까?
아니요, 이전 버전의 플랫폼에는 새로운 기능을 소급하여 추가 할 수 없습니다.
파일 / 폴더에 액세스 할 수있는 앱을 볼 수 있습니까?
현재이를 표시하는 UI가 없지만 adb shell dumpsys activity providers
출력 의 "제공된 URI 권한"섹션에서 세부 사항을 찾을 수 있습니다 .
동일한 기기에서 여러 사용자에게 앱을 설치하면 어떻게 되나요?
Uri 권한 부여는 다른 모든 다중 사용자 플랫폼 기능과 마찬가지로 사용자별로 분리됩니다. 즉, 두 명의 다른 사용자로 실행되는 동일한 앱에는 Uri 권한 부여가 겹치거나 공유되지 않습니다.
권한을 취소 할 수 있습니까?
백업 DocumentProvider는 클라우드 기반 문서가 삭제 될 때와 같이 언제든지 권한을 취소 할 수 있습니다. 취소 된 권한을 발견하는 가장 일반적인 방법은 ContentResolver.getPersistedUriPermissions()
위에서 언급 한 권한이 사라지는 것 입니다.
보조금과 관련된 앱에 대한 앱 데이터가 삭제 될 때마다 권한도 취소됩니다.
선택한 폴더에서 권한 작업을 재귀 적으로 요청 하시겠습니까?
그러나 ACTION_OPEN_DOCUMENT_TREE
의도는 기존 파일과 새로 작성된 파일 및 디렉토리 모두에 대한 재귀 액세스를 제공합니다.
다중 선택이 가능합니까?
그러나 KitKat 이후 다중 선택이 지원되었으며 의도를 EXTRA_ALLOW_MULTIPLE
시작할 때 설정하여 허용 할 수 있습니다 ACTION_OPEN_DOCUMENT
. 또는 선택할 수있는 파일 형식을 사용 Intent.setType()
하거나 EXTRA_MIME_TYPES
좁힐 수 있습니다.
http://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT
에뮬레이터에서 새로운 API를 시도하는 방법이 있습니까?
그러나 기본 공유 저장 장치는 에뮬레이터에서도 선택기에 나타납니다. 앱이 공유 스토리지에 액세스하기 위해 Storage Access Framework 만 사용하는 경우 더 이상 READ/WRITE_EXTERNAL_STORAGE
권한이 필요하지 않으며 권한 을 제거하거나이 android:maxSdkVersion
기능을 사용하여 이전 플랫폼 버전에서만 요청할 수 있습니다.
사용자가 SD 카드를 다른 카드로 교체하면 어떻게됩니까?
실제 미디어가 포함 된 경우 기본 미디어의 UUID (예 : FAT 일련 번호)는 항상 반환 된 Uri에 레코딩됩니다. 사용자가 여러 슬롯 사이에서 미디어를 교체하더라도 시스템은이를 사용하여 사용자가 원래 선택한 미디어에 연결합니다.
사용자가 두 번째 카드를 교환하면 새 카드에 액세스하라는 메시지가 표시됩니다. 시스템은 UUID 단위로 보조금을 기억하므로 사용자가 나중에 다시 삽입하면 원래 카드에 대한 액세스 권한이 계속 부여됩니다.
http://en.wikipedia.org/wiki/Volume_serial_number
아래 링크 된 Github의 Android 프로젝트에서 Android 5의 extSdCard에 쓸 수있는 작업 코드를 찾을 수 있습니다. 사용자가 전체 SD 카드에 액세스 할 수 있다고 가정 한 다음이 카드의 모든 곳에 쓸 수 있습니다. (단일 파일에만 액세스하려는 경우 작업이 쉬워집니다.)
메인 코드 스 니트
스토리지 액세스 프레임 워크 트리거 :
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void triggerStorageAccessFramework() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS);
}
Storage Access Framework의 응답 처리 :
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public final void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) {
if (requestCode == SettingsFragment.REQUEST_CODE_STORAGE_ACCESS) {
Uri treeUri = null;
if (resultCode == Activity.RESULT_OK) {
// Get Uri from Storage Access Framework.
treeUri = resultData.getData();
// Persist URI in shared preference so that you can use it later.
// Use your own framework here instead of PreferenceUtil.
PreferenceUtil.setSharedPreferenceUri(R.string.key_internal_uri_extsdcard, treeUri);
// Persist access permissions.
final int takeFlags = resultData.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
}
}
}
Storage Access Framework를 통해 파일에 대한 outputStream 가져 오기 (저장된 URL을 사용하는 경우 외부 SD 카드의 루트 폴더 URL 인 경우)
DocumentFile targetDocument = getDocumentFile(file, false);
OutputStream outStream = Application.getAppContext().
getContentResolver().openOutputStream(targetDocument.getUri());
이것은 다음과 같은 헬퍼 메소드를 사용합니다.
public static DocumentFile getDocumentFile(final File file, final boolean isDirectory) {
String baseFolder = getExtSdCardFolder(file);
if (baseFolder == null) {
return null;
}
String relativePath = null;
try {
String fullPath = file.getCanonicalPath();
relativePath = fullPath.substring(baseFolder.length() + 1);
}
catch (IOException e) {
return null;
}
Uri treeUri = PreferenceUtil.getSharedPreferenceUri(R.string.key_internal_uri_extsdcard);
if (treeUri == null) {
return null;
}
// start with root of SD card and then parse through document tree.
DocumentFile document = DocumentFile.fromTreeUri(Application.getAppContext(), treeUri);
String[] parts = relativePath.split("\\/");
for (int i = 0; i < parts.length; i++) {
DocumentFile nextDocument = document.findFile(parts[i]);
if (nextDocument == null) {
if ((i < parts.length - 1) || isDirectory) {
nextDocument = document.createDirectory(parts[i]);
}
else {
nextDocument = document.createFile("image", parts[i]);
}
}
document = nextDocument;
}
return document;
}
public static String getExtSdCardFolder(final File file) {
String[] extSdPaths = getExtSdCardPaths();
try {
for (int i = 0; i < extSdPaths.length; i++) {
if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
return extSdPaths[i];
}
}
}
catch (IOException e) {
return null;
}
return null;
}
/**
* Get a list of external SD card paths. (Kitkat or higher.)
*
* @return A list of external SD card paths.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String[] getExtSdCardPaths() {
List<String> paths = new ArrayList<>();
for (File file : Application.getAppContext().getExternalFilesDirs("external")) {
if (file != null && !file.equals(Application.getAppContext().getExternalFilesDir("external"))) {
int index = file.getAbsolutePath().lastIndexOf("/Android/data");
if (index < 0) {
Log.w(Application.TAG, "Unexpected external file dir: " + file.getAbsolutePath());
}
else {
String path = file.getAbsolutePath().substring(0, index);
try {
path = new File(path).getCanonicalPath();
}
catch (IOException e) {
// Keep non-canonical path.
}
paths.add(path);
}
}
}
return paths.toArray(new String[paths.size()]);
}
/**
* Retrieve the application context.
*
* @return The (statically stored) application context
*/
public static Context getAppContext() {
return Application.mApplication.getApplicationContext();
}
전체 코드에 대한 참조
과
It is just a complementary answer.
After you created a new file, you might need to save its location into your database and read it tomorrow. You can read retrieve it again using this method:
/**
* Get {@link DocumentFile} object from SD card.
* @param directory SD card ID followed by directory name, for example {@code 6881-2249:Download/Archive},
* where ID for SD card is {@code 6881-2249}
* @param fileName for example {@code intel_haxm.zip}
* @return <code>null</code> if does not exist
*/
public static DocumentFile getExternalFile(Context context, String directory, String fileName){
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + directory);
DocumentFile parent = DocumentFile.fromTreeUri(context, uri);
return parent != null ? parent.findFile(fileName) : null;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SettingsFragment.REQUEST_CODE_STORAGE_ACCESS && resultCode == RESULT_OK) {
int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
getContentResolver().takePersistableUriPermission(data.getData(), takeFlags);
String sdcard = data.getDataString().replace("content://com.android.externalstorage.documents/tree/", "");
try {
sdcard = URLDecoder.decode(sdcard, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// for example, sdcardId results "6312-2234"
String sdcardId = sdcard.substring(0, sdcard.indexOf(':'));
// save to preferences if you want to use it later
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.edit().putString("sdcard", sdcardId).apply();
}
}
'program story' 카테고리의 다른 글
객체의 필드를 일반 사전 키로 사용 (0) | 2020.07.27 |
---|---|
C ++ 11에서 COW std :: string 구현의 적법성 (0) | 2020.07.27 |
자바 스크립트를 통해 버튼 클릭시 태그의 (0) | 2020.07.27 |
비트 버킷에 리포지토리를 먼저 만들지 않고 SourceTree를 사용하여 로컬 리포지를 비트 버킷에 푸시하려면 어떻게합니까? (0) | 2020.07.27 |
Redis 캐시 및 메모리 직접 사용 (0) | 2020.07.27 |