本文是Android 11 从沙盒拷贝文件到外部共享存储区域 的兄弟篇:Android 11 从外部存储读取文件到应用沙盒存储,效果:
Android10之前,访问外部存储目录即SDCard目录只需要
Environment.getExternalStorageDirectory().getAbsolutePath(),再通过new File()的形式访问。
Android 10 开始,Google建议开发者使用存储访问框架访问外部存储。
使用“存储访问框架”打开文件 | Android 开发者 | Android Developers
new File()的形式只能访问自己应用的沙盒存储路径如:
Context.getExternalFilesDir():SDCard/Android/data/应用包名/files/ 目录
Context.getExternalCacheDir(): SDCard/Android/data/应用包名/cache/目录
Context.getCacheDir():/data/data//cache目录
Context.getFilesDir(): /data/data//files目录
等......
Android 11以上,开始强制不能再通过new File()的形式访问外部存储区域了。
1. FileHandlePresenter.java实现。 封装方法:发Intent拉起文件选择器 文件选择后得到Uri拷贝文件到应用包名目录下 后续根据自己业务需求操作该拷贝后的文件如重命名,解压等等......
package com.mikel.projectdemo.presenter;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class FileHandlePresenter {
public static final String TAG = "FileHandlePresenter";
public static final int PERMISSION_CODE_READ_EXTERNAL = 1;
public static final int REQUEST_CODE_READ_FILE_FROM_EXTERNAL = 1000;
private Fragment mFragment;
public FileHandlePresenter(Fragment fragment) {
mFragment = fragment;
}
/**
* 打开文件选择器
*/
public void requestReadExternalStorage() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
//指定多种类型的文件
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
mFragment.startActivityForResult(Intent.createChooser(intent, "选择文件"), REQUEST_CODE_READ_FILE_FROM_EXTERNAL);
}
/**
* 通过uri拷贝外部存储的文件到自己包名的目录下
* @param uri
* @param destFile
*/
private void copyFieUriToInnerStorage(Uri uri, File destFile) {
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = mFragment.getActivity().getContentResolver().openInputStream(uri);
if(destFile.exists()) {
destFile.delete();
}
fileOutputStream = new FileOutputStream(destFile);
byte[] buffer = new byte[4096];
int redCount;
while ((redCount = inputStream.read(buffer)) >= 0) {
fileOutputStream.write(buffer, 0, redCount);
}
} catch (Exception e) {
Log.e(TAG, " copy file uri to inner storage e = " + e.toString());
} finally {
try {
if(fileOutputStream != null) {
fileOutputStream.flush();
fileOutputStream.getFD().sync();
fileOutputStream.close();
}
if(inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
Log.e(TAG, " close stream e = " + e.toString());
}
}
}
/**
* 申请权限回调
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) {
if(requestCode == PERMISSION_CODE_READ_EXTERNAL) {
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
requestReadExternalStorage();
}
}
}
/**
* 文件选择后回调
* @param requestCode
* @param resultCode
* @param data
*/
public void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
if(resultCode == Activity.RESULT_OK) {
switch (requestCode) {
case REQUEST_CODE_READ_FILE_FROM_EXTERNAL:
try {
Uri fileUri = data.getData();
File destFile = File.createTempFile("temp", ".tmp", mFragment.getActivity().getCacheDir());
Log.d(TAG, " read external storage file = "+ fileUri.toString() + ", dest path = " + destFile.getAbsolutePath());
copyFieUriToInnerStorage(fileUri, destFile);
//todo 外部存储的文件uri 已变成了 应用包下的文件destFile,后续可以new File操作destFile
Toast.makeText(mFragment.getActivity(), "Read External Storage file Success! Save Path = " + destFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
break;
}
}
}
}
2. Ui Fragment 负责Ui 展示、调用 FileHandlePresenter和动态权限申请......
package com.mikel.projectdemo.uiframework.subtab;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.mikel.projectdemo.R;
import com.mikel.projectdemo.presenter.FileHandlePresenter;
import org.jetbrains.annotations.NotNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
public class SubTabFragment1 extends Fragment {
FileHandlePresenter fileHandlePresenter;
public static SubTabFragment1 build() {
return new SubTabFragment1();
}
@Override
public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_sub_tab_content1, null, true);
fileHandlePresenter = new FileHandlePresenter(this);
initUI(rootView);
return rootView;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
fileHandlePresenter.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
fileHandlePresenter.onActivityResult(requestCode, resultCode, data);
}
private void initUI(View rootView) {
Button readFileBtn = rootView.findViewById(R.id.read_file_from_external_btn);
readFileBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* android 6.0以上动态权限申请
*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(getActivity(),
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, FileHandlePresenter.PERMISSION_CODE_READ_EXTERNAL);
} else {
fileHandlePresenter.requestReadExternalStorage();
}
}
});
Button writeFileBtn = rootView.findViewById(R.id.write_file_to_external_btn);
writeFileBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
3. 另外不要忘了app工程的AndroidManifest.mxl下需要声明权限
Demo地址:
GitHub - mikelhm/MikelProjectDemo: Personal Android Demo
MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)_xiaobaaidaba123的专栏-CSDN博客
android 嵌套ViewPager + Fragment实现仿头条UI框架Demo_xiaobaaidaba123的专栏-CSDN博客
Android 使用ViewPager2+ExoPlayer+VideoCache 实现仿抖音视频翻页播放_xiaobaaidaba123的专栏-CSDN博客