1、Android10加入了分区存储, Android11后会强制执行,执行的后果就是无法使用file来访问非应用包名目录下的文件。
2、只需要在清单文件中的application中加入这行android:requestLegacyExternalStorage="true"
即可强制关闭分区存储(Android10有效 Android11不行),设为true则为开启分区存储(用来提前测试分区存储,开启后记得卸载app重新安装一下,不然有问题)。
我的测试机就是Android10的。
文件的uri地址是这种类型的:content://com.android.providers.media.documents/document/video%3A68638
1、打开图片选择页面
首先是选择单张图片 看代码,通过意图intent来打开系统的相册选择界面,这个是Android官网的代码,选择单张图片。
private static final int READ_REQUEST_CODE = 42;
/**
* Fires an intent to spin up the "file chooser" UI and select an image.
* 选取单个图片
*/
public void performFileSearch() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");//定义了选择图片,可以换成别的MIME类型试试
//如将image换成video,则可以选择视频上传
startActivityForResult(intent, READ_REQUEST_CODE);
}
2、获取所选择图片的uri
选择好以后返回到activity中,这个时候在onActivityResult中会得到intent返回的选中的文件的uri
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
switch (requestCode){
case READ_REQUEST_CODE://上传一张图片
Uri uri;
if (data != null) {
uri = data.getData();//文件的uri地址
//获取到uri后可在这里执行上传操作 upload
}
break;
}
}
}
3、获取文件在手机上的绝对路径
这个时候得到了文件的uri,那么,我们需要从uri中获取文件的名字,那么我们调用我们的FileUtils类来获取文件的绝对路径。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
switch (requestCode){
case READ_REQUEST_CODE://上传一张图片
Uri uri;
if (data != null) {
uri = data.getData();//文件的uri地址
String path=FileUtils.getPath(this,uri);//获取文件绝对路径
//这个FileUtils类会在后面放上代码,现在只说文件的上传。
}
break;
}
}
}
4、通过文件的绝对路径来获取文件名(带后缀)
直接将得到的路径放入file类中,虽然file类不能访问外部存储空间,但是可以通过这个file类来获取这个绝对路径文件对于的文件名及后缀
如xxxx.jpg
String fileName=new File(path).getName;//文件名 XXX.JPG
5、获取文件的mime类型,这个可以通过uri来获取,代码如下
String Mimetype = getContentResolver().getType(uri);
6、文件File的获取(无法获取) 看下一步
之后有了文件名 文件类型 ,那么还差一个文件File。
当然在分区存储中是无法用file的api来访问外部存储的,那么,我们就不能传递一个File类了
RequestBody requestBody = RequestBody.create(file, MediaType.parse(type));//我们无法使用file将图片写入相册
7、只能通过byte[] 字节流的方式来传递file文件了。我们可以通过ContentResolver来获取当前文件uri对于的输入流,所以将输入流转为字节流后再进行传输。这么一来就传输完成了。
//OkHttp上传单个文件
void upload(Uri uri){
InputStream inputStream=null;
try {
//client
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.build();
String path = FileUtils.getPath(this, uri);//通过uri得到了文件的绝对路径
File file = new File(path);//特定新建一个file类
String fileName = file.getName();//来获取文件名 主要是文件名的后缀
inputStream = getContentResolver().openInputStream(uri);
byte[] bytes = FileUtils.toByteArray(inputStream);
String type = getContentResolver().getType(uri);
RequestBody requestBody = RequestBody.create(bytes, MediaType.parse(type));
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart("file",fileName, requestBody)
.build();
Request request = new Request.Builder()
.url(baseUrl+"/file/upload")
.post(multipartBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Toast("请求失败"+e);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Toast("成功了:"+response.body().string());
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.e("TAG", "Uri: "+uri.toString() );
}
FileUtils.class
package com.xunua.networkproject.Utils;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @user XUNUA
* @email [email protected]
* @creat time 2020/5/6 12:21
*/
public class FileUtils {
private static String TAG = "FileUtils";
/**
* 专为Android4.4设计的从Uri获取文件绝对路径
*/
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
if (id != null && id.startsWith("raw:")) {
return id.substring(4);
}
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
Log.w(TAG,contentUri+"");
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Log.w(TAG,docId);
Log.w(TAG,type);
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
Log.e(TAG,"isMediaDocument");
final String selection = "_id=?";
final String[] selectionArgs = new String[]{split[1]};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
Log.e(TAG,"content");
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
Log.e(TAG,"file");
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
Log.w(TAG,"hh:"+uri);
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
Log.w(TAG,"hh1:"+cursor);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* 将inputStream转换成字节数组
* @param input
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024*4];
int length = 0;
while (-1 != (length = input.read(buffer))) {
Log.e(TAG, "toByteArray: "+length);
output.write(buffer, 0, length);
}
output.close();
input.close();
return output.toByteArray();
}
/**
*
* @param context 上下文
* @param fileName 文件的名称 xxx.jpg
* @param inputStream 从网络中或者其他地方读取到的要写入的图片的输入流
* @param Subfolders 可以理解为相册的名字 如MyPic 如果为null,则在相册的Pictures文件夹下存放图片
* @return boolean true为存储成功 false为存储失败
*/
public static boolean saveImage(Context context,String fileName,InputStream inputStream,String Subfolders){
ContentResolver contentResolver = context.getContentResolver();
Uri contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME,fileName);//文件名
contentValues.put(MediaStore.Images.Media.MIME_TYPE,"image/*");//文件类型
if (Subfolders == null) {
Subfolders="";
}
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH,"Pictures/"+Subfolders);
Uri insert = contentResolver.insert(contentUri, contentValues);
if (insert == null) {
Log.e("TAG", "onResponse: 异常");
return false;
}
OutputStream outputStream = null;
try {
outputStream = contentResolver.openOutputStream(insert);//写入图片的流
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
byte[] bytes = new byte[1024*2];
int len;
while ((len=inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
outputStream.flush();
}catch (IOException e){
Log.e(TAG, "saveImage: "+e );
}finally {
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
}
OkHttpUtils.class
package com.xunua.networkproject.Utils;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* @user XUNUA
* @email [email protected]
* @creat time 2020/4/26 1:32
*
* post请求参照此:https://blog.csdn.net/w605283073/article/details/103797118?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158784284619725222431143%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=158784284619725222431143&biz_id=0&utm_source=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v25-1
*
* //okhttp3
* implementation("com.squareup.okhttp3:okhttp:4.5.0")
*
*/
public class OkHttpUtils {
/**
* get请求(常用)
* @param url url参数是链接地址
* @return
* @throws IOException
*/
public String get(String url) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.callTimeout(100000, TimeUnit.MILLISECONDS)
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
/**
* Post请求(常用) 这个是key value形式
* 标准请求,将post请求数据写在hashmap中 进行遍历
* @param url url参数是链接地址
* @param hashMap 这个是post请求的key value hashmap.put("请求key1","value1") hashmap.put("请求key2","value2")
* @return
* @throws IOException
*/
public String post(String url, HashMap<String,String> hashMap) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.callTimeout(100000, TimeUnit.MILLISECONDS)
.build();
FormBody.Builder builder = new FormBody.Builder();
for (String key:
hashMap.keySet()) {
builder.add(key, hashMap.get(key));
}
RequestBody body = builder
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
/**
* post请求 这个是json数据形式
*
*这个post请求是发送json数据来进行请求 与上面的不一样 (这个不常用) 只是官网有这个方法,就留在这里
*/
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
String post(String url, String json) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.callTimeout(100000, TimeUnit.MILLISECONDS)
.build();
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
/**
* OkHttp上传单个文件
* @param uri 接口地址
* @param context 上下文
* @param fileUri 文件的uri地址 不再是path路径了 分区存储后只能先得到文件的uri
* @return 返回response 记得判断返回值是否为null
*/
public static Response upload(String uri, Context context,Uri fileUri){
InputStream inputStream=null;
Response response=null;
try {
//client
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.build();
String path = FileUtils.getPath(context, fileUri);//通过uri得到了文件的绝对路径
File file = new File(path);//特定新建一个file类
String fileName = file.getName();//来获取文件名 主要是文件名的后缀
inputStream = context.getContentResolver().openInputStream(fileUri);
byte[] bytes = FileUtils.toByteArray(inputStream);
String type = context.getContentResolver().getType(fileUri);
RequestBody requestBody = RequestBody.create(bytes, MediaType.parse(type));
MultipartBody multipartBody = new MultipartBody.Builder()
.addFormDataPart("file",fileName, requestBody)
.build();
Request request = new Request.Builder()
.url(String.valueOf(uri))
.post(multipartBody)
.build();
response = client.newCall(request).execute();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
/**
* OkHttp上传多个文件 支持上传视频
* @param uri 接口的uri地址
* @param fileUris 存放上传文件的uri地址的集合
* @param context 上下文
*/
void uploads(String uri,List<Uri> fileUris,Context context){
InputStream inputStream=null;
try {
//client
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.build();
MultipartBody.Builder mulBuilder = new MultipartBody.Builder();
for (Uri fileUri:
fileUris) {
String path = FileUtils.getPath(context,fileUri);
File file = new File(path);
String fileName = file.getName();
inputStream=context.getContentResolver().openInputStream(fileUri);
byte[] bytes = FileUtils.toByteArray(inputStream);
RequestBody requestBody = RequestBody.create(bytes, MediaType.parse(context.getContentResolver().getType(fileUri)));
mulBuilder.addFormDataPart("files",fileName,requestBody);
}
MultipartBody multipartBody = mulBuilder.build();
Request request = new Request.Builder()
.url(uri)
.post(multipartBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
另附上我的git仓库地址,内有okhttp来发送get请求post请求 文件上传 多个文件上传 文件下载的实例:
https://gitee.com/linxunyou/NetWorkProject