Android10 Retrofit图片上传

Android10文件存储机制修改成了沙盒模式,应用不能直接访问除了沙盒文件和公共文件以外的文件,直接使用图片绝对地址上传图片会出错,可以通过图片的Uri来上传图片

    private fun copyFromUri(uri:Uri,fileName:String,subscribe:Observer<String>){
        var cacheFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath+File.separator+"cache")
        if(!cacheFile.exists())
            cacheFile.exists()
        cacheFile = File(cacheFile.absolutePath + File.separator + fileName)

        Observable.create<String>{
            val inStream:InputStream = contentResolver.openInputStream(uri)!!
            val outStream:OutputStream = FileOutputStream(cacheFile)
            val bytes = ByteArray(1024)
            var len = -1
            try {
                while ({len = inStream.read(bytes);len}()!=-1)
                    outStream.write(bytes,0,len)
            } catch (e: Exception) {
                it.onError(e)
            } finally {
                inStream.close()
                outStream.close()
            }
            it.onNext(cacheFile.path)
            it.onComplete()
        }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscribe)
    }

上传图片

    private fun onUpload(path: String){
        if ( "" != path.trim() ){
            val file = File(path)
            val body = file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
            val progressBody = ProgressRequestBody(body){
                progress.progress = it.toInt()
            }
            val part = MultipartBody.Part.createFormData("file",file.name,progressBody)
            getRetrofit().create(Api::class.java).onUpload(part)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(object : Observer<ResponseBody> {
                    override fun onSubscribe(d: Disposable) {

                    }

                    override fun onNext(t: ResponseBody) {
                        Toast.makeText(this@MainActivity,"Success",Toast.LENGTH_SHORT).show()
                    }

                    override fun onError(e: Throwable) {
                        Toast.makeText(this@MainActivity,"Error!"+e.message,Toast.LENGTH_SHORT).show()
                    }

                    override fun onComplete() {
                        if(file.exists())
                            file.delete()
                    }
                })
        }
    }

网络接口定义

@Multipart
    @POST("downloadApp/upload")
    fun onUpload(@Part part:MultipartBody.Part):Observable<ResponseBody>

实现进度更新

class ProgressRequestBody(private val requestBody: RequestBody,private val listener:((Long)->Unit) ):RequestBody() {

    private var byteBufferSink:BufferedSink ?= null

    override fun contentType(): MediaType? {
        return requestBody.contentType()
    }

    override fun contentLength(): Long {
        return requestBody.contentLength()
    }

    override fun writeTo(sink: BufferedSink) {
        if (byteBufferSink == null)
            byteBufferSink = sink(sink).buffer()
        requestBody.writeTo(byteBufferSink!!)
        byteBufferSink?.flush()
    }

    private fun sink(sink:Sink) = object : ForwardingSink(sink) {

        private var count = 0L
        private var total = -1L

        @SuppressLint("CheckResult")
        override fun write(source: Buffer, byteCount: Long) {
            if (total ==-1L) total = contentLength()
            super.write(source, byteCount)
            count += byteCount
            val progress = count/total.toDouble() * 100
            Observable.just(count).observeOn(AndroidSchedulers.mainThread()).subscribe {
                listener.invoke(progress.toLong())
            }
        }
    }
}

web端的图片上传代码

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");

        String realPath = userPath + "/upload/test";
        String cachePath = userPath + "/upload/cache";

        File realFile = new File(realPath);
        File cacheFile = new File(cachePath);

        System.out.println("上传路径是:"+realPath);
        if (!realFile.exists())
            realFile.mkdirs();
        if (!cacheFile.exists())
            cacheFile.mkdirs();

        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setRepository(cacheFile);

        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("UTF-8");

        if (!ServletFileUpload.isMultipartContent(request))
            return;

        InputStream in = null;
        OutputStream out = null;
        List<String> files = new ArrayList<>();
        try {
            List<FileItem> items = upload.parseRequest(request);
            for (FileItem item:items){
                if (item.isFormField()){
                    System.out.println(item.getFieldName()+": "+item.getString("UTF-8"));
                }else {
                    String fileName = item.getName();
                    if (fileName.isEmpty()||"".equals(fileName.trim()))
                        continue;
                    fileName = fileName.substring(fileName.lastIndexOf("/")+1);
                    String downloadPath = realPath+"/"+fileName;
                    files.add(fileName);

                    in = item.getInputStream();
                    out = new FileOutputStream(downloadPath);

                    byte bt[] = new byte[1024];

                    int len = -1;

                    while ((len = in.read(bt))!=-1)
                        out.write(bt,0,len);
                }
                item.delete();
            }
        } catch (FileUploadException | IOException e) {
            System.out.println(e.getMessage());
            response.getWriter().write("ERROR");
        } finally {
            if (in!=null)
                in.close();
            if (out!=null)
                out.close();
        }
        response.getWriter().write("SUCCESS");
    }

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 6,212评论 1赞 20
  • ?开启? 【iAPP实现进入界面实行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 3,840评论 0赞 10
  • 7.1 压缩图片 一、基础常识 1、图片的格式 jpg:最常见的图片格式。色彩还原度比较好,可以支撑适当压缩后保持...
    AndroidMaster阅读 1,747评论 0赞 12
  • 这节课是 Android 开发(入门)课程 的第四部分《数据与数据库》的第三节课,导师依然是 Jessica Li...
    HsuJin阅读 363评论 0赞 2
  • content provider管理了对数据中央仓库存储的访问。大家可以说通过在清单文件中的配置和在app中的一个...
    天街孤独阅读 123评论 0赞 0