为融云即时通讯添加发送文件功能

前言

一个月前的事了,融云即时通讯在聊天界面并没有集成发送文件的功能,这个要我们自己来实现,如果你向官方提交工单问这个问题,他们会给你一个地址:http://www.2cto.com/kf/201512/455767.html,
然而其中的代码只是继承了实现的方法,具体实现步骤还是要自己来做,看了发送文字和图片发送的代码以及官方SDK中三个类:MessageProvider,MessageContent,ExtendProvider.有所领悟,分享一下聊天界面发送文件的实现过程


代码

对了,实现有一个前提,那就是确保融云其他功能代码没有错误, 当然有错误也没有关系

  • FileMessage

    rongcloud:
    自定义消息类里面主要做了消息的持久序列化 和 消息包含的内容和成员, 例如上传文件我肯定需要知道文件的本地路径,例如发送红包消息我们需要知道红包的金额

    liompei:
    其实就是用来保存文件地址信息,在这里我保存了两个地址,一个是发送者文件地址,用于上传文件至我们服务器;一个是上传服务器后服务器返回给我们的文件远程地址,用于点击消息后下载文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    import android.annotation.SuppressLint;
    import android.net.Uri;
    import android.os.Parcel;
    import com.fenghua.traffic.utlis.Z;
    import org.json.JSONException;
    import org.json.JSONObject;
    import io.rong.common.ParcelUtils;
    import io.rong.imlib.MessageTag;
    import io.rong.imlib.model.MessageContent;
    import io.rong.imlib.model.UserInfo;
    /**
    * Created by BLM on 2016/6/20.
    */
    @SuppressLint("ParcelCreator")
    @MessageTag(value = "RC:FileMsg",
    flag = 3
    )
    public class FileMessage extends MessageContent {
    private Uri mLocalUri; //发送者文件地址
    private Uri mRemoteUri; //服务器文件地址
    //不明确 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理
    public static final Creator<FileMessage> CREATOR = new Creator() {
    public FileMessage createFromParcel(Parcel source) {
    return new FileMessage(source);
    }
    public FileMessage[] newArray(int size) {
    return new FileMessage[size];
    }
    };
    //get set 方法
    public Uri getmRemoteUri() {
    return mRemoteUri;
    }
    public void setmRemoteUri(Uri mRemoteUri) {
    this.mRemoteUri = mRemoteUri;
    }
    public Uri getmLocalUri() {
    return mLocalUri;
    }
    public void setmLocalUri(Uri mLocalUri) {
    this.mLocalUri = mLocalUri;
    }
    public FileMessage(byte[] data) {
    String jsonStr = new String(data);
    try {
    JSONObject e = new JSONObject(jsonStr);
    if (e.has("local")) {
    Z.log("本地地址不为空");
    String urilocal = e.optString("local");
    setmLocalUri(Uri.parse(urilocal));
    } else {
    Z.log("本地地址为空");
    }
    if (e.has("remote")) {
    Z.log("远程地址不为空");
    String uriremote = e.optString("remote");
    setmRemoteUri(Uri.parse(uriremote));
    } else {
    Z.log("远程地址为空");
    }
    } catch (JSONException e1) {
    e1.printStackTrace();
    }
    }
    //构造函数 初始化传入的 Parcel
    public FileMessage(Parcel in) {
    this.setmLocalUri((Uri) ParcelUtils.readFromParcel(in, Uri.class));
    this.setmRemoteUri((Uri) ParcelUtils.readFromParcel(in, Uri.class));
    this.setUserInfo((UserInfo) ParcelUtils.readFromParcel(in, UserInfo.class));
    }
    public FileMessage(Uri mLocalUri, Uri mRemoteUri) {
    this.mLocalUri = mLocalUri;
    this.mRemoteUri = mRemoteUri;
    }
    //通过此方法获取到fileMessage对象
    public static FileMessage getFileMessage(Uri mLocalUri, Uri mRemoteUri) {
    FileMessage fileMessage = new FileMessage(mLocalUri, mRemoteUri);
    return fileMessage;
    }
    @Override
    public byte[] encode() {
    JSONObject jsonObject = new JSONObject();
    try {
    jsonObject.put("local", getmLocalUri().toString());
    jsonObject.put("remote", getmRemoteUri().toString());
    if (this.getJSONUserInfo() != null) {
    jsonObject.putOpt("user", this.getJSONUserInfo());
    }
    } catch (JSONException e) {
    e.printStackTrace();
    }
    return jsonObject.toString().getBytes();
    }
    //没有操作
    @Override
    public int describeContents() {
    return 0;
    }
    //向服务器写入包数据?
    @Override
    public void writeToParcel(Parcel parcel, int i) {
    ParcelUtils.writeToParcel(parcel, this.getmLocalUri());
    ParcelUtils.writeToParcel(parcel, this.getmRemoteUri());
    ParcelUtils.writeToParcel(parcel, this.getUserInfo());
    }
    }

如果只是用于发送文件,使用两个Uri足以

  • FileMessageProvider

    rongcloud:
    FileMessageProvider 负责控制 UI 展示样式的类 此处就用一个文件样式做展示,如果是发红包可能资源图片就会换成红包样式的图片,还有上传进度的 UI 展示也在本类

    liompei:
    当你选择发送文件,当判断消息类型为我们定义的文件时,在消息上进行显示,分发送方和接收方两个样式,看代码就明白了~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    import android.content.Context;
    import android.content.DialogInterface;
    import android.support.v4.app.FragmentActivity;
    import android.text.Spannable;
    import android.text.SpannableString;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import com.fenghua.traffic.R;
    import com.fenghua.traffic.im.entry.FileMessage;
    import io.rong.imkit.RongContext;
    import io.rong.imkit.RongIM;
    import io.rong.imkit.SendImageManager;
    import io.rong.imkit.model.ProviderTag;
    import io.rong.imkit.model.UIMessage;
    import io.rong.imkit.userInfoCache.RongUserInfoManager;
    import io.rong.imkit.widget.ArraysDialogFragment;
    import io.rong.imkit.widget.provider.IContainerItemProvider;
    import io.rong.imlib.RongIMClient;
    import io.rong.imlib.model.Message;
    import io.rong.imlib.model.UserInfo;
    /**
    * Created by BLM on 2016/6/21.
    */
    @ProviderTag(
    messageContent = FileMessage.class,
    showPortrait = true, showProgress = false
    )
    public class FileMessageProvider extends IContainerItemProvider.MessageProvider<FileMessage> {
    public FileMessageProvider() {
    }
    //绑定
    @Override
    public void bindView(View view, int i, FileMessage fileMessage, UIMessage uiMessage) {
    TheViewHolder mHolder = (TheViewHolder) view.getTag();
    //发送方和接收方
    if (uiMessage.getMessageDirection() == Message.MessageDirection.SEND) {
    view.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_no_right);
    } else {
    view.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_no_left);
    }
    //资源
    mHolder.img.setImageResource(R.drawable.file);
    //进度 ?
    //可在此更新进度条,通过网络
    int progress = uiMessage.getProgress();
    Message.SentStatus status = uiMessage.getSentStatus();
    if (status.equals(Message.SentStatus.SENDING) && progress < 100) {
    if (progress == 0) {
    mHolder.message.setText(RongContext.getInstance().getResources().getString(io.rong.imkit.R.string.rc_waiting));
    } else {
    mHolder.message.setText(progress + "%");
    }
    mHolder.message.setVisibility(View.VISIBLE); //0
    } else {
    mHolder.message.setVisibility(View.GONE); //8
    }
    }
    //设置外部显示名称
    @Override
    public Spannable getContentSummary(FileMessage fileMessage) {
    return new SpannableString(RongContext.getInstance().getResources().getString(R.string.wenjian));
    }
    @Override
    public void onItemClick(View view, int i, FileMessage fileMessage, UIMessage uiMessage) {
    }
    @Override
    public void onItemLongClick(View view, int i, FileMessage fileMessage, final UIMessage uiMessage) {
    String name = null;
    if (uiMessage.getSenderUserId() != null) {
    UserInfo userInfo = uiMessage.getUserInfo();
    if (userInfo == null) {
    userInfo = RongUserInfoManager.getInstance().getUserInfo(uiMessage.getSenderUserId());
    }
    if (userInfo != null) {
    name = userInfo.getName();
    }
    }
    String[] items = new String[]{view.getContext().getResources().getString(io.rong.imkit.R.string.rc_dialog_item_message_delete)};
    ArraysDialogFragment.newInstance(name, items).setArraysDialogItemListener(new ArraysDialogFragment.OnArraysDialogItemListener() {
    public void OnArraysDialogItemClick(DialogInterface dialog, int which) {
    if (which == 0) {
    SendImageManager.getInstance().cancelSendingImage(uiMessage.getConversationType(), uiMessage.getTargetId(), uiMessage.getMessageId());
    RongIM.getInstance().deleteMessages(new int[]{uiMessage.getMessageId()}, (RongIMClient.ResultCallback) null);
    }
    }
    }).show(((FragmentActivity) view.getContext()).getSupportFragmentManager());
    }
    //在此绑定布局文件
    @Override
    public View newView(Context context, ViewGroup viewGroup) {
    View view = LayoutInflater.from(context).inflate(R.layout.de_item_file_message, (ViewGroup) null);
    TheViewHolder mHolder = new TheViewHolder();
    mHolder.message = (TextView) view.findViewById(io.rong.imkit.R.id.rc_msg);
    mHolder.img = (ImageView) view.findViewById(R.id.rc_img);
    view.setTag(mHolder);
    return view;
    }
    class TheViewHolder {
    ImageView img;
    TextView message;
    }
    }

    newView中绑定的布局文件为自己所建,就是你发送消息时显示的样式,可以参考官方类TextMessageItemProvider中的方法

  • SendFileProvider

    rongcloud:
    本类是发送文件的入口类,需要提醒的是. 此处代码中是直接开启的 Android 系统的文件管理器,在某些基于 Android 自定义的机型里面打开没有文件 建议用 Android原生机型 三星小米也都没有问题. 这里自己可以自定义扫描过滤写自定义的文件管理器

    liompei:
    ~~此类就是在扩展功能中添加一个选择文件的图标,定义点击图标后选择文件,startActivityForResult方便获取文件路径,这里我没有在此类中获取返回值,而是在在聊天界面.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.drawable.Drawable;
    import android.view.View;
    import com.fenghua.traffic.utlis.Z;
    import io.rong.imkit.RongContext;
    import io.rong.imkit.widget.provider.InputProvider;
    /**
    * Created by BLM on 2016/6/16.
    */
    public class SendFileProvider extends InputProvider.ExtendProvider {
    private static final String TAG = SendFileProvider.class.getSimpleName();
    /**
    * 实例化适配器。
    *
    * @param context 融云IM上下文。(通过 RongContext.getInstance() 可以获取)
    */
    public SendFileProvider(RongContext context) {
    super(context);
    }
    @Override
    public Drawable obtainPluginDrawable(Context context) {
    return context.getResources().getDrawable(io.rong.imkit.R.drawable.rc_ic_picture);
    }
    @Override
    public CharSequence obtainPluginTitle(Context context) {
    return "文件";
    }
    @Override
    public void onPluginClick(View view) {
    //打开文件管理器
    Intent i = new Intent();
    i.setAction(Intent.ACTION_GET_CONTENT);
    i.setType("*/*");
    startActivityForResult(i, 1);
    // Z.show("点击了文件");
    }
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) {
    Z.log("返回值不匹配");
    return;
    }
    }
    }
  • 在聊天界面操作返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) {
    Z.log("返回值不匹配");
    return;
    }
    if (data.getData() != null){
    Z.log("Liaotian: 收到了可用返回值");
    if (data.getData().getScheme().equals("content")){
    Uri uri = data.getData(); // uri
    final String path = getPath(MyApplication.instance(), uri);
    Z.log(path);
    final Uri mpath = Uri.parse(path);
    File file = new File(path); //可打开的文件路径
    toInter(file,mpath);
    }else if (data.getData().getScheme().equals("file")){
    Uri uri=data.getData();
    String path=data.getData().getPath();
    Z.log(path);
    Uri mpath=Uri.parse(path);
    File file=new File(path);
    toInter(file,mpath);
    }else {
    Z.show("该文件管理器不支持发送文件");
    return;
    }
    }
    }
    private void toInter(File file, final Uri mpath){
    progressDialog.show();
    HttpUtils httpUtils = new HttpUtils();
    RequestParams params = new RequestParams();
    params.addBodyParameter("msgno", "007001");
    params.addBodyParameter("file", file);
    httpHandler=httpUtils.send(HttpRequest.HttpMethod.POST, MyApplication.HOST + "fileUpload.do", params, new RequestCallBack<String>() {
    @Override
    public void onLoading(long total, long current, boolean isUploading) {
    super.onLoading(total, current, isUploading);
    Z.log(total + " " + current + " " + isUploading);
    Z.log((int) (((float) current / total) * 100) + "");
    progressDialog.setProgress((int) (((float) current / total) * 100));
    }
    @Override
    public void onSuccess(ResponseInfo<String> responseInfo) {
    progressDialog.dismiss();
    Z.log("发送成功!");
    Z.log(responseInfo.result + "");
    try {
    JSONObject jsonObject = new JSONObject(responseInfo.result);
    Uri uri1 = Uri.parse(MyApplication.HOST + "upload/" + PreferencesUtils.isNull(jsonObject, "data"));
    showChat(mpath, uri1);
    } catch (JSONException e) {
    e.printStackTrace();
    }
    }
    @Override
    public void onFailure(HttpException e, String s) {
    Z.log("发送失败!");
    Z.log(e.getMessage() + " " + s);
    progressDialog.dismiss();
    Toast.makeText(LiaotianActivity.this, "发送文件失败!", Toast.LENGTH_SHORT).show();
    }
    });
    }
    /**
    * 显示在界面上
    */
    public void showChat(Uri mLocal, Uri mRemote) {
    FileMessage fileMessage = FileMessage.getFileMessage(mLocal, mRemote);
    Conversation.ConversationType mConversationType = Conversation.ConversationType.valueOf(getIntent().getData()
    .getLastPathSegment().toUpperCase(Locale.getDefault()));
    if (RongIM.getInstance().getRongIMClient() != null) {
    RongIM.getInstance().sendMessage(mConversationType, targetId, fileMessage, null, null, new RongIMClient.SendMessageCallback() {
    @Override
    public void onError(Integer integer, RongIMClient.ErrorCode errorCode) {
    Z.log("失败");
    }
    @Override
    public void onSuccess(Integer integer) {
    Z.log("成功");
    }
    });
    }
    }
    /**
    * android 4.4以上和以下版本使用自带文件管理器打开本地文件路径不同,的解决方法
    *
    * @param context
    * @param uri
    * @return
    */
    @TargetApi(Build.VERSION_CODES.KITKAT) //版本
    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];
    }
    // TODO handle non-primary volumes
    }
    // DownloadsProvider
    else if (isDownloadsDocument(uri)) {
    final String id = DocumentsContract.getDocumentId(uri);
    final Uri contentUri = ContentUris.withAppendedId(
    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    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];
    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 getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
    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;
    final String column = "_data";
    final String[] projection = {
    column
    };
    try {
    cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
    null);
    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;
    }
    /**
    * @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());
    }

    由于Android4.4(API19)以上的手机可以使用自带管理器直接发送文件,获取的返回值为Uri路径, 需要使用ContentResolver转为文件路径;再将文件上传到自己的服务器,上传成功后获取到远程地址,再实例化FileMessage,使用融云SDK中”发送一条消息”的方法传入对应值.

    此时发送文件功能已经可以使用了