干货来啦~!
想在聊天中发 小视频?gif 动图? 发红包? 发 自定义表情? 没有问题!在融云统统都可以实现! 以上不管是 小视频 还是 gif 还是 红包 或者是 自定义表情 归根结底都是数据的传输 文件的传输. 后缀无非是 .png .gif .mp4 .amr 等 所以我们只要掌握了文件发送上面的所有需求都能根据文件消息衍生实现。 那我们就来赶紧切入正题看看文件消息的实现和文件消息的收发吧 Let’s Go!
先来看看效果:
接下来这边会把是实现原理 和 实现代码给大家分享
实现原理:
- 消息需要自定义消息类型 使用融云的消息收发通道
- UI展示 需要使用到 融云消息模板 可以自定义UI展示样式
- 利用 后台存储 或者 云存储(此处示例七牛) 上传的文件 再给予这个文件一个 可下载的公网路径(例:http://xxx & https://xxx)
- 发消息其实就是把本地文件上传(你需要有个 upLoadFile 类似的方法), 以及上传进度提示
- 收到消息 用户点击消息 此时就是获取消息体中包含的 retomeUrl 然后去下载 (你需要有个 downLoadFile 类似的方法) 下载下来以后对文件然后根据自身 App 逻辑需求自行处理
实现代码:
- FileMessage 此类是 文件消息 实体类 继承自融云 MessageContent
- FileMessageProvider 此类是 文件消息模板 负责消息在会话界面 UI 的展示
- SendFileProvider 此类是发送文件消息的入口
以上是三个最主要的逻辑代码类, 先大概说下用途. 还有一些细节代码.后续会跟上代码再做详细分享
FileMessage
package io.rong.message;
import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Parcel;
import android.text.TextUtils;
import android.util.Log;
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 Bob on 15/12/24.
*/
@SuppressLint("ParcelCreator")
@MessageTag(value = "RC:FileMsg", flag = MessageTag.ISCOUNTED | MessageTag.ISPERSISTED, messageHandler = FileMessageHandler.class)
public class FileMessage extends MessageContent {
private Uri mThumUri;
private Uri mLocalUri;
private Uri mRemoteUri;
private boolean mUpLoadExp = false;
private String mBase64;
boolean mIsFull;
protected String extra;
/**
* 获取消息附加信息
*
* @return 附加信息
*/
public String getExtra() {
return extra;
}
/**
* 设置消息附加信息
*
* @param extra 附加信息
*/
public void setExtra(String extra) {
this.extra = extra;
}
public FileMessage(byte[] data) {
String jsonStr = new String(data);
try {
JSONObject jsonObj = new JSONObject(jsonStr);
if (jsonObj.has("fileUri")) {
String uri = jsonObj.optString("fileUri");
if(!TextUtils.isEmpty(uri))
setRemoteUri(Uri.parse(uri));
if (getRemoteUri() != null && getRemoteUri().getScheme() != null && getRemoteUri().getScheme().equals("file")) {
setLocalUri(getRemoteUri());
}
}
if (jsonObj.has("content")) {
setBase64(jsonObj.optString("content"));
}
if (jsonObj.has("extra")) {
setExtra(jsonObj.optString("extra"));
}
if (jsonObj.has("exp")) {
setUpLoadExp(true);
}
if (jsonObj.has("isFull")) {
setIsFull(jsonObj.optBoolean("isFull"));
}
if (jsonObj.has("user")) {
setUserInfo(parseJsonToUserInfo(jsonObj.getJSONObject("user")));
}
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
}
public FileMessage() {
}
private FileMessage(Uri thumbUri, Uri localUri) {
mThumUri = thumbUri;
mLocalUri = localUri;
}
private FileMessage(Uri thumbUri, Uri localUri, boolean original) {
mThumUri = thumbUri;
mLocalUri = localUri;
mIsFull = original;
}
/**
* 生成FileMessage对象。
*
*/
public static FileMessage obtain(Uri thumUri, Uri localUri) {
return new FileMessage(thumUri, localUri);
}
/**
* 生成FileMessage对象。
*
*/
public static FileMessage obtain(Uri thumUri, Uri localUri, boolean isFull) {
return new FileMessage(thumUri, localUri, isFull);
}
/**
* 生成FileMessage对象。
*
* @return ImageMessage对象实例。
*/
public static FileMessage obtain() {
return new FileMessage();
}
/**
* 获取缩略图Uri。
*
* @return 缩略图Uri(收消息情况下此为内部Uri,需要通过ResourceManager.getInstance().getFile(new Resource(Uri))方式才能获取到真实地址)。
*/
public Uri getThumUri() {
return mThumUri;
}
/**
*
* @return true / false
*/
public boolean isFull() {
return mIsFull;
}
/**
* 设置发送原图标志位。
*
*/
public void setIsFull(boolean isFull) {
this.mIsFull = isFull;
}
/**
* 设置缩略图Uri。
*
* @param thumUri 缩略图地址
*/
public void setThumUri(Uri thumUri) {
this.mThumUri = thumUri;
}
/**
* 获取本地图片地址(file:///)。
*
* @return 本地图片地址(file:///)。
*/
public Uri getLocalUri() {
return mLocalUri;
}
/**
* 设置本地图片地址(file:///)。
*
* @param localUri 本地图片地址(file:///).
*/
public void setLocalUri(Uri localUri) {
this.mLocalUri = localUri;
}
/**
* 获取网络图片地址(http://)。
*
* @return 网络图片地址(http://)。
*/
public Uri getRemoteUri() {
return mRemoteUri;
}
/**
* 设置网络图片地址(http://)。
*
* @param remoteUri 网络图片地址(http://)。
*/
public void setRemoteUri(Uri remoteUri) {
this.mRemoteUri = remoteUri;
}
/**
* 设置需要传递的Base64数据
*
* @param base64 base64数据。
*/
public void setBase64(String base64) {
mBase64 = base64;
}
/**
* 获取需要传递的Base64数据。
*
* @return base64数据。
*/
public String getBase64() {
return mBase64;
}
/**
* 是否上传失败。
*
* @return 是否上传失败。
*/
public boolean isUpLoadExp() {
return mUpLoadExp;
}
/**
* 设置是否上传失败。
*
* @param upLoadExp 上传是否失败。
*/
public void setUpLoadExp(boolean upLoadExp) {
this.mUpLoadExp = upLoadExp;
}
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
if (!TextUtils.isEmpty(mBase64)) {
jsonObj.put("content", mBase64);
} else {
Log.d("ImageMessage", "base64 is null");
}
if (mRemoteUri != null) {
jsonObj.put("fileUri", mRemoteUri.toString());
} else if (getLocalUri() != null) {
jsonObj.put("fileUri", getLocalUri().toString());
}
if (mUpLoadExp) {
jsonObj.put("exp", true);
}
jsonObj.put("isFull", mIsFull);
if (!TextUtils.isEmpty(getExtra()))
jsonObj.put("extra", getExtra());
if (getJSONUserInfo() != null)
jsonObj.putOpt("user", getJSONUserInfo());
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
mBase64 = null;
return jsonObj.toString().getBytes();
}
/**
* 描述了包含在 Parcelable 对象排列信息中的特殊对象的类型。
*
* @return 一个标志位,表明Parcelable对象特殊对象类型集合的排列。
*/
@Override
public int describeContents() {
return 0;
}
/**
* 构造函数。
*
* @param in 初始化传入的 Parcel。
*/
public FileMessage(Parcel in) {
setExtra(ParcelUtils.readFromParcel(in));
mLocalUri = ParcelUtils.readFromParcel(in, Uri.class);
mRemoteUri = ParcelUtils.readFromParcel(in, Uri.class);
mThumUri = ParcelUtils.readFromParcel(in, Uri.class);
setUserInfo(ParcelUtils.readFromParcel(in, UserInfo.class));
mIsFull = ParcelUtils.readIntFromParcel(in) == 1;
}
/**
* 将类的数据写入外部提供的 Parcel 中。
*
* @param dest 对象被写入的 Parcel。
* @param flags 对象如何被写入的附加标志,可能是 0 或 PARCELABLE_WRITE_RETURN_VALUE。
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
ParcelUtils.writeToParcel(dest, getExtra());
ParcelUtils.writeToParcel(dest, mLocalUri);
ParcelUtils.writeToParcel(dest, mRemoteUri);
ParcelUtils.writeToParcel(dest, mThumUri);
ParcelUtils.writeToParcel(dest, getUserInfo());
ParcelUtils.writeToParcel(dest, mIsFull ? 1 : 0);
}
/**
* 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。
*/
public static final Creator<FileMessage> CREATOR = new Creator<FileMessage>() {
@Override
public FileMessage createFromParcel(Parcel source) {
return new FileMessage(source);
}
@Override
public FileMessage[] newArray(int size) {
return new FileMessage[size];
}
};
}
自定义消息类里面主要做了消息的持久序列化 和 消息包含的内容和成员, 例如上传文件我肯定需要知道文件的本地路径,例如发送红包消息我们需要知道红包的金额
FileMessageProvider
package io.rong.app.message.provider;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.sea_monster.resource.Resource;
import io.rong.app.R;
import io.rong.imkit.RongContext;
import io.rong.imkit.model.ProviderTag;
import io.rong.imkit.model.UIMessage;
import io.rong.imkit.widget.AsyncImageView;
import io.rong.imkit.widget.provider.IContainerItemProvider;
import io.rong.imlib.model.Message;
import io.rong.message.FileMessage;
/**
* Created by Bob on 15/12/24.
*/
@ProviderTag(messageContent = FileMessage.class, showPortrait = true, showProgress = true, centerInHorizontal = false)
public class FileMessageProvider extends IContainerItemProvider.MessageProvider<FileMessage> {
/**
* 初始化View
*/
@Override
public View newView(Context context, ViewGroup group) {
View view = LayoutInflater.from(context).inflate(R.layout.de_item_file_message, group,false);
ViewHolder holder = new ViewHolder();
holder.message = (TextView) view.findViewById(R.id.rc_msg);
holder.img = (AsyncImageView) view.findViewById(R.id.rc_img);
view.setTag(holder);
return view;
}
@Override
public void bindView(View v, int position, FileMessage content, UIMessage message) {
final ViewHolder holder = (ViewHolder) v.getTag();
if (message.getMessageDirection() == Message.MessageDirection.SEND) {
v.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_no_right);
} else {
v.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_no_left);
}
holder.img.setResource(content.getThumUri() == null ? null : new Resource(content.getThumUri()));
int progress = message.getProgress();
Message.SentStatus status = message.getSentStatus();
if (status.equals(Message.SentStatus.SENDING) && progress < 100) {
if (progress == 0)
holder.message.setText(RongContext.getInstance().getResources().getString(io.rong.imkit.R.string.rc_waiting));
else
holder.message.setText(progress + "%");
holder.message.setVisibility(View.VISIBLE);
} else {
holder.message.setVisibility(View.GONE);
}
}
@Override
public Spannable getContentSummary(FileMessage data) {
return new SpannableString(RongContext.getInstance()
.getResources()
.getString(R.string.de_plugins_file));
}
@Override
public void onItemClick(View view, int position, FileMessage content, UIMessage message) {
}
@Override
public void onItemLongClick(View view, int position, FileMessage content, UIMessage message) {
}
class ViewHolder {
AsyncImageView img;
TextView message;
}
}
FileMessageProvider 负责控制 UI 展示样式的类 此处就用一个文件样式做展示,如果是发红包可能资源图片就会换成红包样式的图片,还有上传进度的 UI 展示也在本类
SendFileProvider
package io.rong.app.message.provider;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import java.io.File;
import io.rong.imkit.RongContext;
import io.rong.imkit.RongIM;
import io.rong.imkit.widget.provider.InputProvider;
import io.rong.imlib.RongIMClient;
import io.rong.imlib.model.Conversation;
import io.rong.imlib.model.Message;
import io.rong.message.FileMessage;
/**
* Created by AMing on 15/12/24.
* Company RongCloud
*/
public class SendFileProvider extends InputProvider.ExtendProvider {
private static final String TAG = SendFileProvider.class.getSimpleName();
private Context context;
/**
* 实例化适配器。
*
* @param context 融云IM上下文。(通过 RongContext.getInstance() 可以获取)
*/
public SendFileProvider(RongContext context) {
super(context);
this.context = 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 intent = new Intent(Intent.ACTION_GET_CONTENT);
// intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
// startActivityForResult(intent, 1);
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("file/*");
startActivityForResult(i, 1);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null) {
if (data.getData().getScheme().equals("file")) {
String s = data.getData().getPath();
Uri uri = data.getData();
File file = new File(s);
if (file.exists()) {
Log.e("file", "f是文件且存在");
Conversation conversation = getCurrentConversation();
sendFile(conversation.getConversationType(),conversation.getTargetId(),file,uri);
} else {
Toast.makeText(context,"文件不存在",Toast.LENGTH_SHORT);
}
}
}
}
private void sendFile(Conversation.ConversationType conversationType, String id, File file,Uri uri) {
if (RongIM.getInstance()!= null && RongIM.getInstance().getRongIMClient() != null) {
//TODO 文件消息
Log.e("tag","");
Uri themUri = Uri.parse("file:///sdcard/bob/bob.zip" );
// Uri localUri = Uri.parse("file:///sdcard/bob/bob.zip");
FileMessage fileMessage = FileMessage.obtain(uri,uri);
// ImageMessage fileMessage = ImageMessage.obtain(themUri,localUri);
RongIM.getInstance().getRongIMClient().sendImageMessage(Conversation.ConversationType.PRIVATE, id, fileMessage, null, null, new RongIMClient.SendImageMessageCallback() {
@Override
public void onAttached(Message message) {
Log.e(TAG, "-------------onAttached--------");
}
@Override
public void onError(Message message, RongIMClient.ErrorCode code) {
Log.e(TAG, "----------------onError-----" + code);
}
@Override
public void onSuccess(Message message) {
Log.e(TAG, "------------------onSuccess---");
}
@Override
public void onProgress(Message message, int progress) {
Log.e(TAG, "-----------------onProgress----" + progress);
}
});
}
}
}
本类是发送文件的入口类,需要提醒的是. 此处代码中是直接开启的 Android 系统的文件管理器,在某些基于 Android 自定义的机型里面打开没有文件 建议用 Android原生机型 三星小米也都没有问题. 这里自己可以自定义扫描过滤写自定义的文件管理器
Ohter Code:
三个核心类分享完了 我们再来说说其他相关代码
- application 类中注册消息类型 和 注册消息模板. 这步简单但是很容易忘记
RongIM.registerMessageType(FileMessage.class);
RongIM.registerMessageTemplate(newFileMessageProvider());
- 点击消息的监听内判断文件消息
if(message.getContent() instanceof FileMessage) {
FileMessage fileMessage = (FileMessage) message.getContent();
if (message.getMessageDirection().equals(io.rong.imlib.model.Message.MessageDirection.RECEIVE)) {
Intent intent = new Intent(context, FileActivity.class);
intent.putExtra("photo", fileMessage.getLocalUri() == null ? fileMessage.getRemoteUri() : fileMessage.getLocalUri());
if (fileMessage.getThumUri() != null)
intent.putExtra("thumbnail", fileMessage.getThumUri());
context.startActivity(intent);
}
}
此处判断是文件消息然后跳转至下载页面下载
总结:
实现效果 和 实现原理 还有大概代码都已经在上方分享,但是代码建议大家不要全部 copy 有些东西还是需要自己理解后亲自去实现. 例如上传和下载的方法实现 这个是和你 服务端 或者 云存储交互的逻辑, 最后也希望这篇文章能够帮助大家! End~
有任何意见或者建议欢迎在下方吐槽或评论