![](/img/trans.png)
[英]Open file from specific folder with Intent.ACTION_OPEN_DOCUMENT
[英]How to use Intent.ACTION_OPEN_DOCUMENT in Android Pie
我正在使用 android 餅圖中的 Retrofit 更改個人資料照片 function。
於是我就成功的把用相機拍的照片上傳到了服務器上。 但我不知道如何將從圖庫中選擇的照片傳輸到我的服務器。 (我可以使用 Java Kotlin 中的任何類型的代碼。)
我稍后會上傳視頻。
我在谷歌上搜索了很多,但很難得到我想要的信息。
在谷歌文檔中做得很好,但我不知道該怎么做。 https://developer.android.com/guide/topics/providers/document-provider
谷歌文檔顯示了使用 bitmap 或 inputstream 或其他東西的示例。
我是否需要 bitmap 或輸入流才能使用 Retrofit 上傳照片?
我實際上需要一個有效的 uri。
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE);
}
@Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData) {
if (requestCode == READ_REQUEST_CODE && resultCode ==Activity.RESULT_OK) {
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
showImage(uri);
}
}
}
public void Edit_Profile (String Image_Uri) {
File file = new File(Image_Uri);
RequestBody requestBody = RequestBody.create(file, MediaType.parse("image/*"));
MultipartBody.Part body = MultipartBody.Part.createFormData("uploaded_file", Num+ID+".jpg", requestBody);
}
實際上,onActivityResult 返回以下類型的 uri。
content://com.android.providers.media.documents/document/image%3A191474
因此,當我嘗試使用該 uri 將其發送到我的服務器時,我收到 FileNotFoundException 錯誤。
這是 Android-Q 中引入的隱私限制。 當應用程序以 API 29 為目標並且從getExternalStorageDirectory方法返回的路徑不再可供應用程序直接訪問時,不推薦直接訪問共享/外部存儲設備。 使用特定於應用程序的目錄來寫入和讀取文件。
默認情況下,針對 Android 10 及更高版本的應用程序被授予對外部存儲或范圍存儲的范圍訪問權限。 此類應用程序可以在外部存儲設備中查看以下類型的文件,而無需請求任何與存儲相關的用戶權限:
應用程序特定目錄中的文件,使用 getExternalFilesDir() 訪問。 應用程序從媒體商店創建的照片、視頻和音頻剪輯。
Go 通過文檔使用存儲訪問框架打開文件
談到上下文,您可以做的一件事是,正如 CommonsWare 建議的那樣使用
InputStreamRequestBody
。 否則,將所選文件復制到您的應用程序沙箱文件夾 IE,即應用程序特定目錄,然后在沒有任何權限的情況下從那里訪問該文件。 只需查看以下適用於 Android-Q 及更高版本的實現即可。
執行文件搜索
private void performFileSearch(String messageTitle) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType("application/*");
String[] mimeTypes = new String[]{"application/x-binary,application/octet-stream"};
if (mimeTypes.length > 0) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
}
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(Intent.createChooser(intent, messageTitle), OPEN_DIRECTORY_REQUEST_CODE);
} else {
Log.d("Unable to resolve Intent.ACTION_OPEN_DOCUMENT {}");
}
}
onActivityResult 返回
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code OPEN_DIRECTORY_REQUEST_CODE.
// If the request code seen here doesn't match, it's the response to some other intent,
// and the below code shouldn't run at all.
if (requestCode == OPEN_DIRECTORY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (resultData != null && resultData.getData() != null) {
new CopyFileToAppDirTask().execute(resultData.getData());
} else {
Log.d("File uri not found {}");
}
} else {
Log.d("User cancelled file browsing {}");
}
}
}
文件寫入應用程序特定路徑
public static final String FILE_BROWSER_CACHE_DIR = "CertCache";
@SuppressLint("StaticFieldLeak")
private class CopyFileToAppDirTask extends AsyncTask<Uri, Void, String> {
private ProgressDialog mProgressDialog;
private CopyFileToAppDirTask() {
mProgressDialog = new ProgressDialog(YourActivity.this);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgressDialog.setMessage("Please Wait..");
mProgressDialog.show();
}
protected String doInBackground(Uri... uris) {
try {
return writeFileContent(uris[0]);
} catch (IOException e) {
Log.d("Failed to copy file {}" + e.getMessage());
return null;
}
}
protected void onPostExecute(String cachedFilePath) {
mProgressDialog.dismiss();
if (cachedFilePath != null) {
Log.d("Cached file path {}" + cachedFilePath);
} else {
Log.d("Writing failed {}");
}
}
}
private String writeFileContent(final Uri uri) throws IOException {
InputStream selectedFileInputStream =
getContentResolver().openInputStream(uri);
if (selectedFileInputStream != null) {
final File certCacheDir = new File(getExternalFilesDir(null), FILE_BROWSER_CACHE_DIR);
boolean isCertCacheDirExists = certCacheDir.exists();
if (!isCertCacheDirExists) {
isCertCacheDirExists = certCacheDir.mkdirs();
}
if (isCertCacheDirExists) {
String filePath = certCacheDir.getAbsolutePath() + "/" + getFileDisplayName(uri);
OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
int length;
while ((length = selectedFileInputStream.read(buffer)) > 0) {
selectedFileOutPutStream.write(buffer, 0, length);
}
selectedFileOutPutStream.flush();
selectedFileOutPutStream.close();
return filePath;
}
selectedFileInputStream.close();
}
return null;
}
// Returns file display name.
@Nullable
private String getFileDisplayName(final Uri uri) {
String displayName = null;
try (Cursor cursor = getContentResolver()
.query(uri, null, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i("Display Name {}" + displayName);
}
}
return displayName;
}
這是一個可能更通用的解決方案,它允許 AsyncTask class 是獨立的,而不是嵌入到活動中。 它還會在任務完成時返回對您的活動的響應,並使用 ProgressBar 而不是已棄用的 ProgressDialog。
異步任務:
public class FileLoader extends AsyncTask<Uri, Void, String>
{
private WeakReference<Context> contextRef;
public AsyncResponse delegate = null;
public interface AsyncResponse {
void fileLoadFinish(String result);
}
FileLoader(Context ctx , AsyncResponse delegate) {
contextRef = new WeakReference<>(ctx);
this.delegate = delegate;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
protected String doInBackground(Uri... uris) {
Context context = contextRef.get();
ContentResolver contentResolver = context.getContentResolver();
Uri uri = uris[0];
try {
String mimeType = contentResolver.getType(uri);
Cursor returnCursor =
contentResolver.query(uri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String fileName = returnCursor.getString(nameIndex);
InputStream inputStream = contentResolver.openInputStream(uri);
File downloadDir =
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File f = new File(downloadDir + "/" + fileName);
FileOutputStream out = new FileOutputStream(f);
IOUtils.copyStream(inputStream,out);
returnCursor.close();
return f.getPath();
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
protected void onPostExecute(String result) {
delegate.fileLoadFinish(result);
super.onPostExecute(result);
}
}
在您的活動中:
private static final int DIR_ACCESS_REQUEST_CODE = 13;
public void performFileSearch() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/*");
String[] mimeTypes = new String[]{"application/gpx+xml","application/vnd.google-earth.kmz"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(Intent.createChooser(intent, "Choose KMZ or GPX file"), DIR_ACCESS_REQUEST_CODE);
} else {
Log.d("****File","Unable to resolve Intent.ACTION_OPEN_DOCUMENT");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode,Intent resultData)
{
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == DIR_ACCESS_REQUEST_CODE && resultCode == Activity.RESULT_OK)
{
if (resultData != null)
{
Uri uri = resultData.getData();
mProgressBar.setVisibility(View.VISIBLE);
new FileLoader(this,
new FileLoader.AsyncResponse(){
@Override
public void fileLoadFinish(String result){
processFile(new File(result));
mProgressBar.setVisibility(View.GONE);
}
}).execute(uri);
}
}
}
我的示例嘗試查找.kmz 文件或.gpx 文件。 進度條(如果需要,用於長時間運行的文件操作)需要在 OnCreate() 中初始化(並隱藏):
mProgressBar = findViewById(R.id.progressbar);
mProgressBar.setVisibility(View.GONE);
我的“processFile()”方法在主要活動中操作 map 需要一段時間,所以我一直等到它完成后才隱藏 ProgressBar。
我仍然驚訝於執行如此簡單的操作需要如此多的代碼:復制文件並使其可供使用!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.