纠结了好久的文件上传与下载功能,最近终于完美解决。心路历程简直不想提了,说多了都是泪。可能也是自己的知识掌握还不够吧,毕竟,很少做android方面的。

其实,在网上也找了很多关于这方面的资料,但是,可能和我理解的有些出入,项目中也是不能够很好的应用,导致花费了大量的时间。所以,有必要记录一下,怎么说呢?就是希望我踩过的坑能给大家一些帮助。

我自己的项目实例也是结合网上的一些资料,然后运用到自己的项目中的,其实我们需要注意的就是一些细节的地方,今天就先讲关于OKhttp实现文件上传:

咱们还是先看效果再继续往下说:

如果对效果还算是比较满意,那我们继续往下看,东西不多,就是要注意细节咯!

1. xml布局

相信这个就不用我多说了,我的布局也很简单,看到上面的图估计就能猜到。这里就不把代码贴出来了,简单说一下,两个文本框(界面上的 “文献综述内容” 和 “附件”),两个编辑框(就是你看到的两个输入框),两个按钮(选择文件的 “浏览”和 “确定”),轻松搞定。

还有就是读取文件的权限不要忘了。

2. activity中的实现

看到布局之后,想必操作你也是了然于胸了吧,我们需要给 “浏览” 这个按钮设置点击事件的监听,点击它后,需要打开我们的文件夹:

    //打开文件选择器    private void showFileChooser() {        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);        // 设置你要打开的文件type        intent.setType("application/*");        intent.addCategory(Intent.CATEGORY_OPENABLE);        startActivityForResult(intent, 1);    }

对于,你要设置的setType,可以看看这个参考手册,我这里主要是为了选择文本类的文件,所以我才这么设置的,如果你支持所有类型,也可以改为  intent.setType( "*/*" ),这个看你自己的需要了。

然后,在它的后面别忘了:

    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        Log.w(TAG,"返回的数据:"+data);        if (resultCode == Activity.RESULT_OK) {            Uri uri = data.getData();            //使用第三方应用打开            if ("file".equalsIgnoreCase(uri.getScheme())){                path = uri.getPath();                file = new File(path);                uploadfile = file.getName();                mc_annex.setText(uploadfile);                Log.w(TAG,"getName==="+uploadfile);                Toast.makeText(this,path+"11111",Toast.LENGTH_SHORT).show();                return;            }            //4.4以后            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {                // 获取文件路径                  path = getPath(this, uri);                Log.w(TAG,path);                file = new File(path);                // 获得文件名                uploadfile = file.getName();                // 这里是为了选中文件后,编辑框内容变成我选中的文件名                // 直接 mc_annex.setText(file.getName()); 也行                mc_annex.setText(uploadfile);                Log.w(TAG,"getName==="+uploadfile);                Toast.makeText(this,path,Toast.LENGTH_SHORT).show();            } else {//4.4以下下系统调用方法                path = getRealPathFromURI(this,uri);                Log.w(TAG,path);                Toast.makeText(StudentMiddleCheckEditActivity.this, path+"222222", Toast.LENGTH_SHORT).show();            }        }    }

对了,别忘了定义变量,我是在一开始就定义了,所以,这里没有。然后,uploadfile单纯的就是为了得到文件的名字,因为我后面调接口的时候需要把文件名传过去。

private String path,uploadfile;private File file;

3. FileUtils 封装类

之前,我们能够很明显看到getPath(this, uri)的方法,那么这个方法是封装在哪呢?

我自己遇到的问题是什么,选中文件之后就报这样的错误:

E/AndroidRuntime: FATAL EXCEPTION: main    Process: com.will.manage_system, PID: 22879    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { dat=content://com.android.providers.downloads.documents/document/raw:/storage/emulated/0/Download/会议记录9-12月.doc flg=0x1 }} to activity {com.will.manage_system/com.manage_system.ui.manage.activity.student.StudentMiddleCheckActivity}: java.lang.NumberFormatException: For input string: "raw:/storage/emulated/0/Download/会议记录9-12月.doc"        at android.app.ActivityThread.deliverResults(ActivityThread.java:4360)        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4402)        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)        at android.os.Handler.dispatchMessage(Handler.java:106)        at android.os.Looper.loop(Looper.java:193)        at android.app.ActivityThread.main(ActivityThread.java:6669)        at java.lang.reflect.Method.invoke(Native Method)        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)     Caused by: java.lang.NumberFormatException: For input string: "raw:/storage/emulated/0/Download/会议记录9-12月.doc"        at java.lang.Long.parseLong(Long.java:594)        at java.lang.Long.valueOf(Long.java:808)        at com.manage_system.utils.FileUtils.getPath(FileUtils.java:109)        at com.manage_system.ui.manage.activity.student.StudentMiddleCheckActivity.onActivityResult(StudentMiddleCheckActivity.java:293)        at android.app.Activity.dispatchActivityResult(Activity.java:7454)        at android.app.ActivityThread.deliverResults(ActivityThread.java:4353)        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4402)         at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)         at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)         at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)         at android.os.Handler.dispatchMessage(Handler.java:106)         at android.os.Looper.loop(Looper.java:193)         at android.app.ActivityThread.main(ActivityThread.java:6669)         at java.lang.reflect.Method.invoke(Native Method)         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

喏,再截个图: 

但是,选择系统自带的文件是没有问题的,最后发现传过去的字段不符,其实也就加了一下几行代码:

if (id != null && id.startsWith("raw:")) {     return id.substring(4);}

好吧,直接把我改完之后的代码放上来:

package com.manage_system.utils;import android.annotation.SuppressLint;import android.content.ContentUris;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.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;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;    }    public static String getRealPathFromURI(Context context,Uri contentUri) {        String res = null;        String[] proj = { MediaStore.Images.Media.DATA };        Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);        if(null!=cursor&&cursor.moveToFirst()){            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);            res = cursor.getString(column_index);            cursor.close();        }        return res;    }    /**     * @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());    }}

4. OKhttp 带文件传参

在OkManager里封装了一个方法:

public static void postFile(String url, RequestBody requestBody ,okhttp3.Callback callback)    {        OkHttpClient client = new OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS)                .readTimeout(60000, TimeUnit.MILLISECONDS)                .build();        // 取到我存放在SharedPreferences 的token        SharedPreferences sp=MyApp.getAppContext().getSharedPreferences("loginInfo", MODE_PRIVATE);        Request request = new Request.Builder()                .header("token",sp.getString("token" , ""))                .url(url)                .post(requestBody)                .build();        client.newCall(request).enqueue(callback);    }

第一个client的时间设置,想必有看我之前这篇博文的小伙伴知道,是为了防止我们客户端不能及时响应服务器的数据,当然,不设置也是没啥问题的,只是说如果后台数据量过大,而我们又不能及时处理,可能会存在报错超时的情况。

而我自己将token存放在SharedPreferences里,也是为了添加header,因为自己项目的需要,用户登录成功之后,服务器会返回一个token,请求数据的时候需要将token放到请求头上,所以就是你上面看到的样子。当然了,关于这块,后面我们再详讲。

最后,说一下,requestBody,我把这个参数传进来的原因,是因为我需要在不同的地方调用。

哈哈,去掉了之后的简化版:

public static void postFile(String url, RequestBody requestBody ,okhttp3.Callback callback)    {        OkHttpClient client = new OkHttpClient();        Request request = new Request.Builder()                .url(url)                .post(requestBody)                .build();        client.newCall(request).enqueue(callback);    }

总之,一句话,看你自己的需求。

那具体是怎么调用呢?如下:

OkManager manager = OkManager.getInstance();RequestBody requestBody = new MultipartBody.Builder()                    .setType(MultipartBody.FORM)                    .addFormDataPart("intro", intro) // 提交内容字段                    .addFormDataPart("uploadfile", uploadfile, RequestBody.create(MediaType.parse("*/*"), file)) // 第一个参数传到服务器的字段名,第二个你自己的文件名,第三个MediaType.parse("*/*")和我们之前说的那个type其实是一样的                    .build();manager.postFile(url, requestBody,new okhttp3.Callback() {            @Override            public void onFailure(Call call, IOException e) {                Log.e(TAG, "onFailure: ",e);            }            @Override            public void onResponse(Call call, Response response) throws IOException {                final String responseBody = response.body().string();                final JSONObject obj = JSON.parseObject(responseBody);                Log.e(TAG,obj.toString());                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        // 对返回结果进行操作                    }                });            }        });

对了,看到这我想起来,在OkManager里面还定义了一个获取对象的方法:

    //采用单例模式获取对象    public static OkManager getInstance() {        OkManager instance = null;        if (manager == null) {            synchronized (OkManager.class) {                //同步代码块                if (instance == null) {                    instance = new OkManager();                    manager = instance;                }            }        }        return instance;    }

至此,文件上传应该是没什么问题了。下一篇我们接着讲OKhttp实现文件下载。

更多相关文章

  1. 利用python对android批量多渠道打包
  2. android之SQLite数据库应用(二)
  3. Android通知管理(NotificationManager)的使用,包括震动,led闪屏
  4. 调试方法之打堆栈加重写控件
  5. Android压缩图片到100K以下并保持不失真的高效方法
  6. [转]android 修改ramdisk.img和init.rc && android启动后设置/da
  7. 关于eclipse中关联各版本Android.jar对应的源代码方法
  8. Android——TextView和EditText控件
  9. Android(安卓)设备兼容性使用方法详解

随机推荐

  1. 经典面试题(21):以下代码将输出的结果是什
  2. 经典面试题(18):以下代码将输出的结果是什
  3. Gogland更名为GoLand,并附加新功能
  4. 社区leaf学习笔记|02. leaf服务器文件配
  5. 社区leaf学习笔记|03. 调试Game、Login模
  6. 社区leaf学习笔记|04. MongoDB测试
  7. 轻度Linux服务器维护人员常用的Shell脚本
  8. 社区leaf学习笔记|05. 游戏玩家注册、登
  9. 小程序对IPhone全面屏手机底部黑线的安全
  10. 微信小程序从开发到发布流程