潇洒郎: 解决Android 10 外部存储问题
基于Android 10(Q),即SdkVersion > 28
Android 10存储方式发生改变, Q版本 访问自己应用的专有目录无需读写权限。
媒体收藏(图片,视频,音频等)需要申请权限,并借助MediaStorage
下载(文档与电子书等)无需权限。
方法1—— 最简单:
在应用的专有目录创建文件无需读写权限:
应用的专有目录:
basePath1= "/storage/emulated/0/Android/data/com.example.storage1/files/Documents/";
basePath2= getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()+ "/";
注意: 打印出来的basePath2字符串与basePath1的字符串一模一样。但是basePath1 不等于basePath2。
在Android 10 版本中 不配置权限等 只能用 basePath2, 建议使用basePath2, 超级简单,只是
使用basePath1 这种路径的写法,是Android10 以下版本的写法,需要权限申请,配置方法如方法2.
方法2- 麻烦复杂:
1、 ActivityManifest.xml 配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.storage1">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
android:requestLegacyExternalStorage="true">
<activity android:name=".DefaultActivity"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
关键两点:
2. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="30dp"
>
<EditText
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入数据"
android:layout_marginTop="30dp"
/>
<EditText
android:id="@+id/filepath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="文件名路径"
android:layout_marginTop="30dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<Button
android:id="@+id/save"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="保存数据"
android:textSize="15dp"
android:textStyle="bold"
android:layout_marginTop="30dp"
android:textColor="#E91E63"
android:background="#009688"
android:textAllCaps="false"
/>
<Button
android:id="@+id/show"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="显示数据"
android:textColor="@color/teal_200"
android:background="#B61AD1"
android:textSize="15dp"
android:textStyle="bold"
android:layout_marginTop="30dp"
android:textAllCaps="false"
android:layout_marginLeft="20dp"
/>
</LinearLayout>
<EditText
android:id="@+id/rootpath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="手机root路径"
android:layout_marginTop="30dp"
/>
<EditText
android:id="@+id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="显示文件名路径中的数据"
android:layout_marginTop="30dp" />
</LinearLayout>
视图为:
3. MainActivity.java
package com.example.storage1;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity{
private Button save,show;
private EditText editText, editText2, editTextFilePath, editTextRootPath;
private static String basePath= "/storage/emulated/0/Android/data/";
private static String baseDir="";
private static String fileName="";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
save= (Button) findViewById(R.id.save);
show= (Button) findViewById(R.id.show);
editText=(EditText) findViewById(R.id.text);
editText2=(EditText) findViewById(R.id.text2);
editTextFilePath=(EditText) findViewById(R.id.filepath);
editTextRootPath=(EditText) findViewById(R.id.rootpath);
editTextRootPath.setText(basePath);
// 申请权限,否则手机默认禁止存储
ActivityCompat.requestPermissions(MainActivity.this,new String[ ]{
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"},1);
// 开始逻辑
basePath= basePath + getPackageName()+"/files/";
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String relatedPath= editTextFilePath.getText().toString();
String[] allP= getBaseDirAndFileName(relatedPath);
baseDir = allP[0];
fileName = allP[1];
//获取应用目录File filePictures = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
String str= editText.getText().toString();
F.save(str,baseDir, fileName);
ToastUtil.showMsg(MainActivity.this, "保存数据成功");
}
});
show.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editText2.setText(F.read(baseDir+fileName));
}
});
}
public static String[] getBaseDirAndFileName(String relatedPath){
String[] dirFile= relatedPath.split("/");
int sLength=dirFile.length;
String fileName= dirFile[sLength-1];
String fileDir= "";
for (int i=0;i<sLength-1;i++){
fileDir+=dirFile[i];
fileDir+="/";
}
String[] baseDirFileName={"",""};
baseDirFileName[0]=basePath+fileDir;
baseDirFileName[1]=fileName;
return baseDirFileName;
}
4. ToastUtil.java 用于显示提示信息
package com.example.storage1;
import android.content.Context;
import android.widget.Toast;
public class ToastUtil {
public static Toast mToast;
public static void showMsg(Context context, String msg){
if (mToast==null){
mToast= Toast.makeText(context, msg, Toast.LENGTH_LONG);
}
else{
mToast.setText(msg);
}
mToast.show();
}
}
ToastUtil.showMsg(MainActivity.this, "保存数据成功");
5. 读取与写入文件:
package com.example.storage1;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
public class F {
private static MainActivity mainActivity;
// Android 10 外部存储
// 必须存储至应用/storage/emulated/0/Android/data/包名/files/内
public static void save(String str, String filePath, String fileName){
try{
File fileDir = new File(filePath);
if (fileDir.exists()) {
File file= new File(fileDir,fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(str.getBytes());
fos.flush();
fos.close();
}else if (fileDir.mkdirs()) {//如果该文件夹不存在,则新建
File file= new File(fileDir,fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(str.getBytes());
fos.flush();
fos.close();
}
}catch (IOException e) {e.printStackTrace();}
}
public static String read(String filePath)
{
try {
File file= new File(filePath);
if (!file.exists()){
Log.d("读取的文件夹不存在:",file.toString());
return null;
}
FileInputStream fis= new FileInputStream(file);
byte[] buff=new byte[1024];
StringBuilder sb= new StringBuilder();
int len=0;
while((len=fis.read(buff))>0)
{
sb.append(new String(buff,0,len));
}
fis.close();
return sb.toString();
}
catch (IOException e) {}
return null;
}
}
运行: 效果展示,我用的是手机HUAWEIMate20 Android 10 系统
借鉴博客:https://blog.csdn.net/zhendong_hu/article/details/104921985
https://blog.csdn.net/zw904448290/article/details/114316496
google开发手册: https://developer.android.google.cn/training/data-storage/app-specific#java