---------------------------------------------------------------------------前言-------------------------------------------------------------------------------------

首先先看看自己文件权限加在AndroidManifest了吗,以及是否在代码中动态获取了权限。 看完需再看 Android Q:文件上传(之前的文章有点问题) 有小修改一些此文章中的问题

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

从相册选择图片上传,框架使用的是Rx + Retrofit + OKHttp。因为此版本使用了MVVM架构,targetSdkVersion升到了29。在上传图片出现了报错

java.io.FileNotFoundException: /storage/emulated/0/DCIM/Camera/IMG20200608195140.jpg: open failed: EACCES (Permission denied)

原因是在android10开始,Google修改了文件相关权限,对于写入和读取文件,都有了新的一套机制,介绍内容网上很多,各大厂商也有介绍,在此不做赘述。

此解决方法适用于能获取到文件的Uri,比如打开相册选择图片,那么它的onActivityResult中的Intent data就有选择的图片的Uri。

打开图库的方法不变:

    /*** 选择照片*/public static void pickPhoto(Activity activity) {Intent intent;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {intent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);} else {intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setType("image/*");}activity.startActivityForResult(intent, 2001);}

和onActivityResult回调:

    @Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (data == null) {return;}if (resultCode == Activity.RESULT_OK) {switch (requestCode) {case 2001:Uri originalUri = data.getData();String path = UriUtil.getPath(this, originalUri);if (path != null && new File(path).exists()) {File mFile = new File(path);viewModel.updateImg(mFile);}break;}}}

先附上Android10之前我的图片上传

//从上文中获取了文件
public static List<MultipartBody.Part> getFileBody(File file) {MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);//表单类型RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);//表单类型builder.addFormDataPart("file", file.getName(), body);return builder.build().parts();
}//Retrofit Api 接口/*** 文件上传*/
@Multipart
@POST("xxxx")
Observable<ObjResponse<ShowImageTypeData>> updateFile(@Part List<MultipartBody.Part>     partLis);//调用接口
public Observable<ShowImageTypeData> updateImg(File file) {return api.updateFile(getFileBody(file)).compose(new ObjTransform<>(null));
}

大同小异,主要就是获取文件File类型实例,然后构建RequestBody,使用Retrofit上传

Android Q不行了,会报错,那么主要就是使用到file的地方要修改,先看看RequestBody.create还能带什么参数:

public abstract class RequestBody {/** Returns the Content-Type header for this body. */public abstract MediaType contentType();/*** Returns the number of bytes that will be written to {@code out} in a call to {@link #writeTo},* or -1 if that count is unknown.*/public long contentLength() throws IOException {return -1;}/** Writes the content of this request to {@code out}. */public abstract void writeTo(BufferedSink sink) throws IOException;/*** Returns a new request body that transmits {@code content}. If {@code contentType} is non-null* and lacks a charset, this will use UTF-8.*/public static RequestBody create(MediaType contentType, String content) {Charset charset = Util.UTF_8;if (contentType != null) {charset = contentType.charset();if (charset == null) {charset = Util.UTF_8;contentType = MediaType.parse(contentType + "; charset=utf-8");}}byte[] bytes = content.getBytes(charset);return create(contentType, bytes);}/** Returns a new request body that transmits {@code content}. */public static RequestBody create(final MediaType contentType, final ByteString content) {return new RequestBody() {@Override public MediaType contentType() {return contentType;}@Override public long contentLength() throws IOException {return content.size();}@Override public void writeTo(BufferedSink sink) throws IOException {sink.write(content);}};}/** Returns a new request body that transmits {@code content}. */public static RequestBody create(final MediaType contentType, final byte[] content) {return create(contentType, content, 0, content.length);}/** Returns a new request body that transmits {@code content}. */public static RequestBody create(final MediaType contentType, final byte[] content,final int offset, final int byteCount) {if (content == null) throw new NullPointerException("content == null");Util.checkOffsetAndCount(content.length, offset, byteCount);return new RequestBody() {@Override public MediaType contentType() {return contentType;}@Override public long contentLength() {return byteCount;}@Override public void writeTo(BufferedSink sink) throws IOException {sink.write(content, offset, byteCount);}};}/** Returns a new request body that transmits the content of {@code file}. */public static RequestBody create(final MediaType contentType, final File file) {if (file == null) throw new NullPointerException("content == null");return new RequestBody() {@Override public MediaType contentType() {return contentType;}@Override public long contentLength() {return file.length();}@Override public void writeTo(BufferedSink sink) throws IOException {Source source = null;try {source = Okio.source(file);sink.writeAll(source);} finally {Util.closeQuietly(source);}}};}
}

从上文可以看出除了一个file。其他大部分都是使用RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount)  那么我们也尝试使用byte[]作为上传参数,现在就需要把文件转为byte[]。

直接上代码,从onActivityResult开始:

    @Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);if (data == null) {return;}if (resultCode == Activity.RESULT_OK) {switch (requestCode) {case 2001:Uri originalUri = data.getData();try {ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(originalUri, "r");FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();FileInputStream fis = new FileInputStream(fileDescriptor);ByteArrayOutputStream swapStream = new ByteArrayOutputStream();byte[] buff = new byte[1024*4]; //buff用于存放循环读取的临时数据int rc = 0;while ((rc = fis.read(buff, 0, 100)) > 0) {swapStream.write(buff, 0, rc);}byte[] in_b = swapStream.toByteArray(); //in_b为转换之后的结果viewModel.updateImg(in_b);} catch (IOException e) {e.printStackTrace();}break;}}}

因为我分了好几个文件,包括这些代码也只是暂时写在onActivityResult里,你们只需要看,获取到关键数据后怎么做,然后自己组装一下,应该容易的。在获取到byte[]后,使用byte[]构建RequestBody:

{

底下那个“androidImg”是错的,这个是设置文件名称,假如说你填写了这个的话,那么就无法识别文件类型,后台存储的是一个无类型的文件,你再下载的话无法打开,只用图片上传以及使用Glide框架显示的,没这个问题,给Glide点个赞!

public static List<MultipartBody.Part> getFileBody(byte[] file) {MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);//表单类型RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);//表单类型builder.addFormDataPart("file", "androidImg", body);return builder.build().parts();}

}

重新把构建List<MultipartBody.Part>方法改一下,增加一个文件名,这个文件名称只需要最后有个后缀就行了,前面带文件路径也没关系,这是PostMan调用文件上传接口时的数据:

POST /api/file-application/upload HTTP/1.1
Host: 192.168.16.36:8079
Authorization: Bearer xxxx
cache-control: no-cache
Postman-Token: 2ff47803-aadb-4aeb-90d4-cfe342cbf78e
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="file"; filename="C:\Users\xx\Desktop\QQ截图20190301164159.png------WebKitFormBoundary7MA4YWxkTrZu0gW--说明一下,这个filename="xxx" 这个xxx就是刚刚填写的androidImg

那么文件名获取,也是使用Uri获取文件名:

    /*** 获取对应uri的path** @param context* @param uri* @return*/@SuppressLint("NewApi")public static String getPath(final Context context, final Uri uri) {final boolean isKitKat = Build.VERSION.SDK_INT >= 19;// DocumentProviderif (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {// ExternalStorageProviderif (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];}// handle non-primary volumes}// DownloadsProviderelse if (isDownloadsDocument(uri)) {final String id = DocumentsContract.getDocumentId(uri);final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(id));return getDataColumn(context, contentUri, null, null);}// MediaProviderelse if (isMediaDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0];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;}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())) {// Return the remote addressif (isGooglePhotosUri(uri))return uri.getLastPathSegment();return getDataColumn(context, uri, null, null);}// Fileelse if ("file".equalsIgnoreCase(uri.getScheme())) {return uri.getPath();}return null;}

那么再来构建List<MultipartBody.Part>:

public static List<MultipartBody.Part> getFileBody(byte[] file, String fileName) {MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);//表单类型RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);//表单类型builder.addFormDataPart("file", fileName, body);return builder.build().parts();}

把上面获取到的文件路径传入这个方法中,之后使用Retrofit上没有差别因为接口传入参数类型没变

api.updateFile(AppUtils.getFileBody(file, fileName)).compose(new ObjTransform<>(null));

附上失败和成功的log:

使用file传文件

--> POST http://192.168.16.36:8079/xxxxx http/1.1
Content-Type: multipart/form-data; boundary=36717d28-648b-44b2-a904-07b23fa5fdb8
Content-Length: 115873
Cache-Control: no-cache
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9xxxxxx
Host: 192.168.16.36:8079
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.6.0
--> END POST
<-- HTTP FAILED: java.io.FileNotFoundException: /storage/emulated/0/tieba/FCF10754FF8F4014D623FF8F8B937C1D.jpg: open failed: EACCES (Permission denied)

使用byte[]传文件:

--> POST http://192.168.16.36:8079/xxxxx http/1.1
Content-Type: multipart/form-data; boundary=36717d28-648b-44b2-a904-07b23fa5fdb8
Content-Length: 115873
Cache-Control: no-cache
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9xxxxxx
Host: 192.168.16.36:8079
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.6.0
--> END POST<-- 200  http://192.168.16.36:8079/xxxxx (213ms)
Server: nginx/1.11.6
Date: Wed, 10 Jun 2020 03:38:10 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
<-- END HTTP{"code":1,"subCode":null,"message":"成功","data":xxxxx}

数据返回正常

之后应该会再写一个使用相册拍照的功能。

Android Q:上传图片java.io.FileNotFoundException: open failed: EACCES (Permission denied)相关推荐

  1. Android程序报错:Anroid 6.0 权限问题java.io.FileNotFoundException: ……:open failed: EACCES (Permission denied)

    异常 java.io.FileNotFoundException: /storage/emulated/0/Video/ekwing_main_paren.apk(你的文件路径): open fail ...

  2. java.io.FileNotFoundException: /sdcard/fav.txt (Permission denied)

    对Sdcard进行文件的读写操作的时候,用1.5的SDK安装包运行的时候非常的正常,但是改用2.2的SDK之后问题就比较的多了,就报了下面的错误: java.io.FileNotFoundExcept ...

  3. java.io.FileNotFoundException: /storage/emulated/0/ (Permission denied)

    最近在做项目时保存文件时老是抛java.io.FileNotFoundException: /storage/emulated/0/ (Permission denied)异常 说什么权限不够,我在这 ...

  4. java.io.FileNotFoundException: /storage/emulated/0/xxx.mp3: open failed: EACCES (Permission denied)

    用的是Android10真机,我要给的东西都给了,还是遇到了这个问题. W/System.err: java.io.FileNotFoundException: /storage/emulated/0 ...

  5. FileNotFoundException(/storage/emulated/0/DCIM/Camera/xx.jpg: open failed: EACCES (Permission denied

    android10 模拟器读取相机图片时报错: FileNotFoundException(/storage/emulated/0/DCIM/Camera/xx.jpg: open failed: E ...

  6. Android有文件读写权限,无法读写文件 open failed: EACCES (Permission denied) 获取设备唯一不变id 所有文件读写权限按钮无法打开

    最近有个需求,要获取设备唯一id,Android一直没有什么完善的方案获取设备唯一不变的id,我就结合了几种方案组成了一下: 1. 首先获取IMEI; IMEI获取失败后 2. 获取SN序列号,序列号 ...

  7. Android 仿微信多张图片选择器,适配android10系统,open failed: EACCES (Permission denied)

    实现效果 只需引入模块,比起依赖,更方便自定义 implementation project(':imagepicker') //图片加载 implementation 'com.github.bum ...

  8. open failed: EACCES (Permission denied)

     华为H60-L03  系统4.4.2 app更新时遇见open failed: EACCES (Permission denied) 错误,读写权限都已经注册 java.io.FileNotFo ...

  9. open failed: EACCES (Permission denied)权限已加,写入sd卡仍报错的解决办法

    不知道大家有没有碰到过这种情况,在运行虚拟机时,或者使用高版本的安卓手机时,即使是在清单文件中加了权限,向sd卡写入数据时还是会报错:open failed: EACCES (Permission d ...

最新文章

  1. C语言连续指针_只愿与一人十指紧扣_新浪博客
  2. 测量场效应晶体管(JFET) 2N3819
  3. UVA 12034 Race
  4. 一次性动态绑定多个droplistdown
  5. Vue报错bash: vue: command not found或者vue ui没有反应:官方修改成新的命令了
  6. python 迭代详解_详解python中的迭代
  7. 在Docker上部署NGINX和NGINX Plus
  8. Ext.widgets-menu
  9. 两个约翰的故事--读DOOM启示录
  10. 超好用的手机开源自动化测试工具分享
  11. 计算机文件丢失不能正常启动,电脑说文件丢失或损坏开不了机怎么办?
  12. 城市天际线 for Mac城市建造类游戏
  13. 在马克思手稿中有一道趣味的数学问题:一共有30个人,可能包括男人,女人和小孩。他们在一家饭馆吃饭共花了50先令,其中每个男人花3先令,每个女人花2先令,每个小孩花1先令。请问男人、女人和小孩各几人?
  14. 用JS 来简单计算一下现在距离9月10号还有几天几时几分几秒
  15. STM32 USART串口发送软件调试
  16. 解决VirtualBox虚拟电脑控制台严重错误
  17. 使用Envato Elements Sketch插件立即创建惊人的原型
  18. 计算机图形学常用算法实现11 扫描线z-buffer算法
  19. 论文阅读--Adapted Dynamic Memory Network for Emotion Recognition in Conversation
  20. Latex texstudio+ctex中文解决方案

热门文章

  1. 基于 FFmpeg SDL 的视频播放器的制作 课程的视频
  2. 3个开关与3盏灯的问题
  3. python输入名字配对情侣网名_输入姓名配对情侣网名
  4. AI黑白照片上色系列-藏在英国伦敦图书馆黑白上色,从未发表的100多年前的中国影像
  5. php 真正的多线程 pthread
  6. 神州数码思特奇和学大教育哪个公司比较好?请高手给分析下
  7. matlab中字符表示,(专题一)07 matlab中字符串的表示
  8. 全局变量被未知原因改变的解决方法
  9. RPA Uipath学习心得
  10. C++最小生成树Kruskal算法