前言
就目前市面的项目而已,即时通讯功能算是很普遍的需求深受boos喜爱,boos最喜欢让你撸出一个微信聊天来。当然大多说公司不会自己去搭建一个即时通讯技术栈。更多的是集成第三方功能,站在巨人的肩膀上快速开发出boos要求的社交功能。读完本文你可以在3个小时内作出一个心仪的IM功能(这个当然是对于有一定开发能力的读者而言,初级码仔先搬个小板凳慢慢学哦)...
当然,接入融云的即时通讯功能最好是先看一遍官方文档 https://www.rongcloud.cn/docs/ (这么做可以整体了解集成过程,做到心里有数)。融云本身有为我们开发者设计了界面组件库,可以极大地减少我们的开发时间,在这里我比较建议创业型公司的同学最好先使用他们提供的页面组件,后面研究的比较透彻后可以自己去自定义UI.本文主要是对于自己项目中使用的功能做一个总结,笔者只是小厂码农,也没有去自己写UI,用的是改造官方的,所以大神们可以忽略本文。
我先屡一下大纲
1.注册账号,配置应用信息
首先,在融云官网注册https://www.rongcloud.cn/ 然后创建应用,获取appkey(这个后面的sdk接入会用到)。这些细节我就跳过了。
2.集成融云SDK
1.首先,在融云下载SDK(下载地址:https://www.rongcloud.cn/downloads)我这里只下载了IMLib,IMKit前者是通讯能力库是必须要的,后者是界面组件库,我们可以在它的基础上快速开发。如果你想自己写UI可以不要它。如果还想接入音视频和红包等功能还可以引入CallLib,RedPacket等库,具体怎么选择就看你的项目需求了。我这里只使用了IMLib和IMKit下文也是基于这两者的。
2.根据官方指导采用导入Module的方式,AS中File -> New -> Import Module然后导入IMLib,IMKit。然后在settings.gradle文件中
include ':IMKit',':IMLib'
IMKit里面已经引入IMLib所以我们只需要把IMKit进行引入,在你使用的主项目的build.gradle中
implementation project(':IMKit')
3.在IMLib Module 的 AndroidManifest.xml 文件,把 meta-data RONG_CLOUD_APP_KEY 的值修改为您自己的 AppKey
<meta-data
android:name="RONG_CLOUD_APP_KEY"
android:value="您的应用 AppKey" />
在你的应用的主项目 App Module 的 AndroidManifest.xml 文件中,添加 FileProvider 相关配置,修改 android:authorities 为您的应用的 “ApplicationId”.FileProvider。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/rc_file_path" />
</provider>
在Application里面初始化由于融云SDK有使用多进程所以我建议这样写
if (getApplicationInfo().packageName.equals(getCurProcessName(getApplicationContext()))) {
RongIM.init(this);//init Im
RongIM.setConnectionStatusListener(new RongIMClient.ConnectionStatusListener() {
@Override
public void onChanged(ConnectionStatus status) {
LogUtil.i("MyApplication----->Rongyun onChanged: ---" + status);
if (status == ConnectionStatus.TOKEN_INCORRECT) {
if (!TextUtils.isEmpty(imToken)) {
RongIM.connect(imToken, IMListener.getInstance().getConnectCallback());
} else {
LogUtil.i("token is empty, can not reconnect");
}
}
}
});
}
3.获取token,连接服务器
token需要后端去融云获取,我们测试可以直接拷贝过去,实际使用中不建议这么做,我这里引入融云的原话:
为了方便您在集成和测试过程中使用,我们还提供了 API 调试工具,在您不能部署服务器程序时,可以直接通过传入 userId 和 name 来获得 Token。请访问融云开发者平台,打开您想测试的应用,在左侧菜单中选择“API 调试”即可。
有了token之后可以连接服务器
private void connect(String token) {
if (getApplicationInfo().packageName.equals(App.getCurProcessName(getApplicationContext()))) {
RongIM.connect(token, new RongIMClient.ConnectCallback() {
/**
* Token 错误。可以从下面两点检查 1. Token 是否过期,如果过期您需要向 App Server 重新请求一个新的 Token
* 2. token 对应的 appKey 和工程里设置的 appKey 是否一致
*/
@Override
public void onTokenIncorrect() {
}
/**
* 连接融云成功
* @param userid 当前 token 对应的用户 id
*/
@Override
public void onSuccess(String userid) {
Log.d("LoginActivity", "--onSuccess" + userid);
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
/**
* 连接融云失败
* @param errorCode 错误码,可到官网 查看错误码对应的注释
*/
@Override
public void onError(RongIMClient.ErrorCode errorCode) {
}
});
}
}
处理回调,广播接收器
我这里是把融云的回调统一放在类里面进行处理
public class IMListener implements RongIM.ConversationBehaviorListener, RongIM.ConversationListBehaviorListener, RongIMClient.ConnectionStatusListener, RongIM.UserInfoProvider, RongIM.GroupInfoProvider, RongIMClient.OnReceiveMessageListener, RongIM.IGroupMembersProvider {
public static final String TAG = "IMListener";
private Context mContext;
private static IMListener mInstance;
public static IMListener getInstance() {
return mInstance;
}
private static ArrayList<Activity> mActivities;
public IMListener(Context mContext) {
this.mContext = mContext;
initListener();
mActivities = new ArrayList<>();
}
public static void init(Context context) {
if (mInstance == null) {
synchronized (IMListener.class) {
if (mInstance == null) {
mInstance = new IMListener(context);
}
}
}
}
private void initListener() {
RongIM.setConversationBehaviorListener(this);//设置会话界面操作的监听器。
RongIM.setConversationListBehaviorListener(this);
RongIM.setConnectionStatusListener(this);
RongIM.setUserInfoProvider(this, true);
RongIM.setGroupInfoProvider(this, true);
// RongIM.setLocationProvider(this);//设置地理位置提供者,不用位置的同学可以注掉此行代码
RongIM.setOnReceiveMessageListener(this);//重点收到消息的回调
setInputProvider();
setReadReceiptConversationType();
RongIM.getInstance().enableNewComingMessageIcon(true);
RongIM.getInstance().enableUnreadMessageIcon(true);
RongIM.getInstance().setGroupMembersProvider(this);
setMessageItemLongClickAction(mContext);
}
private static void setMessageItemLongClickAction(Context context) {
MessageItemLongClickAction action = new MessageItemLongClickAction.Builder()
.titleResId(R.string.rc_dialog_item_message_delete)
.actionListener(new MessageItemLongClickAction.MessageItemLongClickListener() {
@Override
public boolean onMessageItemLongClick(Context context, UIMessage message) {
Message[] messages = new Message[1];
messages[0] = message.getMessage();
RongIM.getInstance().deleteMessages(new int[]{message.getMessageId()}, null);
return false;
}
}).build();
RongMessageItemLongClickActionManager.getInstance().addMessageItemLongClickAction(action, 1);
}
private void setReadReceiptConversationType() {
Conversation.ConversationType[] types = new Conversation.ConversationType[]{
Conversation.ConversationType.PRIVATE,
Conversation.ConversationType.GROUP,
Conversation.ConversationType.DISCUSSION
};
RongIM.getInstance().setReadReceiptConversationTypeList(types);
}
/**
* 设置输入面板
*/
private void setInputProvider() {
// 没有特殊需求就忽略
// List<IExtensionModule> moduleList = RongExtensionManager.getInstance().getExtensionModules();
// IExtensionModule defaultModule = null;
// if (moduleList != null) {
// for (IExtensionModule module : moduleList) {
// if (module instanceof DefaultExtensionModule) {
// defaultModule = module;
// break;
// }
// }
// if (defaultModule != null) {
// RongExtensionManager.getInstance().unregisterExtensionModule(defaultModule);
// RongExtensionManager.getInstance().registerExtensionModule(new SealExtensionModule(mContext));
// }
// }
}
public RongIMClient.ConnectCallback getConnectCallback() {
RongIMClient.ConnectCallback connectCallback = new RongIMClient.ConnectCallback() {
@Override
public void onTokenIncorrect() {
//token 错误
LogUtil.i("ImService----->onTokenIncorrect: 融云token错误");
ImService.start(mContext, AppConstant.UPDATE_TOKEN);
}
@Override
public void onSuccess(String userid) {
LogUtil.i("IMListener----->onSuccess: " + userid);
}
@Override
public void onError(final RongIMClient.ErrorCode e) {
LogUtil.i("IMListener----->onError: " + e);
}
};
return connectCallback;
}
/****************** 融云回调 ******************/
/*--------------- 消息交互 --------------*/
@Override
public boolean onUserPortraitClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo) {
//点击头像 这里群组默认都是专家
if (conversationType == Conversation.ConversationType.CUSTOMER_SERVICE || conversationType == Conversation.ConversationType.PUBLIC_SERVICE || conversationType == Conversation.ConversationType.APP_PUBLIC_SERVICE) {
return false;
}
//开发测试时,发送系统消息的userInfo只有id不为空
if (userInfo != null && userInfo.getName() != null && userInfo.getPortraitUri() != null) {
String mTargetId = userInfo.getUserId();
if (mTargetId.length() >= 6) {
try {
String id = mTargetId.substring(5, mTargetId.length());
AcardInfoActivity.show(context, id, AcardInfoActivity.ACARD_FRIENDS);
} catch (Exception e) {
e.printStackTrace();
}
} else {
LogUtil.i("ChatReportActivity----->initBundleData: targetId is not right——>"+mTargetId);
}
}
return true;
}
@Override
public boolean onUserPortraitLongClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo) {
return false;
}
@Override
public boolean onMessageClick(Context context, View view, Message message) {
return false;
}
@Override
public boolean onMessageLinkClick(Context context, String s) {
return false;
}
@Override
public boolean onMessageLongClick(Context context, View view, Message message) {
return false;
}
/*--------------- 消息交互 --------------*/
/*--------------- 会话的交互 --------------*/
@Override
public boolean onConversationPortraitClick(Context context, Conversation.ConversationType conversationType, String s) {
return false;
}
@Override
public boolean onConversationPortraitLongClick(Context context, Conversation.ConversationType conversationType, String s) {
return false;
}
@Override
public boolean onConversationLongClick(Context context, View view, UIConversation uiConversation) {
return false;
}
@Override
public boolean onConversationClick(Context context, View view, UIConversation uiConversation) {
return false;
}
/*--------------- 会话的交互 --------------*/
@Override
public void onChanged(ConnectionStatus connectionStatus) {
Log.d(TAG, "ConnectionStatus onChanged = " + connectionStatus.getMessage());
if (connectionStatus.equals(ConnectionStatus.KICKED_OFFLINE_BY_OTHER_CLIENT)) {
GlobalDialogActivity.start(mContext);
} else if (connectionStatus == ConnectionStatus.TOKEN_INCORRECT) {
// SharedPreferences sp = mContext.getSharedPreferences("config", Context.MODE_PRIVATE);
// final String cacheToken = sp.getString("loginToken", "");
// if (!TextUtils.isEmpty(cacheToken)) {
// RongIM.connect(cacheToken, getConnectCallback());
// } else {
// Log.e("seal", "token is empty, can not reconnect");
// }
}
}
@Override
public UserInfo getUserInfo(String userId) {
IMUserInfoManager.getInstance().getUserInfo(userId);
return null;
}
@Override
public Group getGroupInfo(String groupId) {
String url = AppConstant.URL_STUDIO_TEAM_INFO + "?teamRcId=" + groupId;
HttpUtil.fastGet(url, this, new GsonCallBack<StudioTeamInfoBean>() {
@Override
public void onSuccess(StudioTeamInfoBean bean) {
StudioTeamInfoBean.DataBean data = bean.getData();
Group groupInfo = new Group(data.getTeamRcId(), data.getName(), Uri.parse(AppConstant.BASE_URL + data.getPortrait()));
RongIM.getInstance().refreshGroupInfoCache(groupInfo);
}
@Override
public void onError(Exception e, int erroCode) {
Log.d(TAG, "getGroupInfo==" + e.getMessage());
}
});
return null;
}
@Override
public boolean onReceived(Message message, int i) {
LogUtil.i("融云消息=====" + message.getContent());
MessageContent messageContent = message.getContent();
if (messageContent instanceof DeleteFriendsMessage) {
DeleteFriendsMessage deleteFriendsMessage = (DeleteFriendsMessage) messageContent;
LogUtil.i("IMListener----->onReceived: " + message.toString());
LogUtil.i("IMListener----->onReceived: " + deleteFriendsMessage.getContent());
RongIM.getInstance().clearMessages(Conversation.ConversationType.PRIVATE, deleteFriendsMessage.getContent(), new RongIMClient.ResultCallback<Boolean>() {
@Override
public void onSuccess(Boolean aBoolean) {
RongIM.getInstance().removeConversation(Conversation.ConversationType.PRIVATE, deleteFriendsMessage.getContent(), null);
}
@Override
public void onError(RongIMClient.ErrorCode e) {
}
});
}
else if (messageContent instanceof ContactNotificationMessage) {
ContactNotificationMessage contactNotificationMessage = (ContactNotificationMessage) messageContent;
LogUtil.e("contactNotificationMessage=====" + contactNotificationMessage.getMessage() + contactNotificationMessage.getExtra());
if (contactNotificationMessage.getOperation().equals("Request")) {
//对方发来好友邀请
} else if (contactNotificationMessage.getOperation().equals("AcceptResponse")) {
//对方同意我的好友请求
}
} else if (messageContent instanceof GroupNotificationMessage) {
GroupNotificationMessage groupNotificationMessage = (GroupNotificationMessage) messageContent;
LogUtil.e("groupNotificationMessage----" + "TargetId==\n" + message.getTargetId() + "data===\n" + groupNotificationMessage.getData() +
"getOperation==\n" + groupNotificationMessage.getOperation());
String groupID = message.getTargetId();
MyGroupNotificationMessageData data = null;
try {
String currentID = RongIM.getInstance().getCurrentUserId();
try {
data = jsonToBean(groupNotificationMessage.getData());
data.setTargetGroupId(groupID);
} catch (Exception e) {
e.printStackTrace();
}
if (groupNotificationMessage.getOperation().equals("Create")) {
//创建群组
// SealUserInfoManager.getInstance().getGroups(groupID);
// SealUserInfoManager.getInstance().getGroupMember(groupID);
EventBus.getDefault().post(new ImEvent(ImMessageCode.CREATE,groupID));
} else if (groupNotificationMessage.getOperation().equals("Dismiss")) {
//解散群组
handleGroupDismiss(groupID);
} else if (groupNotificationMessage.getOperation().equals("Kicked")) {
//群组踢人
EventBus.getDefault().post(new ImEvent(ImMessageCode.KICKED,groupID));
} else if (groupNotificationMessage.getOperation().equals("Add")) {
//群组添加人员
EventBus.getDefault().post(new ImEvent(ImMessageCode.ADD,groupID));
} else if (groupNotificationMessage.getOperation().equals("Quit")) {
//退出群组
EventBus.getDefault().post(new ImEvent(ImMessageCode.QUIT,groupID));
} else if (groupNotificationMessage.getOperation().equals("Rename")) {
//群组重命名
EventBus.getDefault().post(new ImEvent<String>(data.getTargetGroupName(),ImMessageCode.RENAME,groupID));
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
} else if (messageContent instanceof ImageMessage) {
//ImageMessage imageMessage = (ImageMessage) messageContent;
}
return false;
}
@Override
public void getGroupMembers(String groupId, final RongIM.IGroupMemberCallback callback) {
//获取群成员 数据库+网络
String url = AppConstant.URL_STUDIO_TEAM_GET_GROUP_LIST;
HttpUtil.fastGet(url + "?teamRcId=" + groupId + "&pageSize=200", this, new GsonCallBack<StudioGroupMemberBean>() {
@Override
public void onSuccess(StudioGroupMemberBean bean) {
StudioGroupMemberBean.DataBean data = bean.getData();
if (data != null) {
List<StudioGroupMemberBean.DataBean.ListBean> list = data.getList();
if (CheckUtil.isNotNull(list)) {
List<UserInfo> userInfos = new ArrayList<>();
for (StudioGroupMemberBean.DataBean.ListBean listBean : list) {
UserInfo userInfo = new UserInfo(listBean.getId(), listBean.getName(), Uri.parse(AppConstant.BASE_URL + listBean.getPortrait()));
userInfos.add(userInfo);
}
callback.onGetGroupMembersResult(userInfos);
}
}
}
@Override
public void onError(Exception e, int erroCode) {
callback.onGetGroupMembersResult(null);
}
});
}
}
我这里只处理了单聊和群聊两种情况,具体其它的应用情景笔者在这里不再给出,读者可以自己看融云文档根据具体使用情景进行功能补充
自定义广播接收器
public class IMNotificationReceiver extends PushMessageReceiver {
@Override
public boolean onNotificationMessageArrived(Context context, PushType pushType, PushNotificationMessage message) {
return false; // 返回 false, 会弹出融云 SDK 默认通知; 返回 true, 融云 SDK 不会弹通知, 通知需要由您自定义。
}
@Override
public boolean onNotificationMessageClicked(Context context, PushType pushType, PushNotificationMessage message) {
return false;// 返回 false, 会走融云 SDK 默认处理逻辑, 即点击该通知会打开会话列表或会话界面; 返回 true, 则由您自定义处理逻辑。
}
@Override
public void onThirdPartyPushState(PushType pushType, String action, long resultCode) {
super.onThirdPartyPushState(pushType, action, resultCode);
}
}
4.在页面组件库的基础上自定义UI
1.会话列表自定义
可以根据IMKit里面的ConversationListFragment修改成自己的MyConversationListFragment,其实我只是把里面的适配器重写了MyConversationListAdapter所以这里重点介绍Adapter
public class MyConversationListAdapter extends BaseAdapter<UIConversation> {
private static final String TAG = "MyConversationListAdapter";
LayoutInflater mInflater;
Context mContext;
private MyConversationListAdapter.OnPortraitItemClick mOnPortraitItemClick;
public long getItemId(int position) {
UIConversation conversation = (UIConversation) this.getItem(position);
return conversation == null ? 0L : (long) conversation.hashCode();
}
public MyConversationListAdapter(Context context) {
this.mContext = context;
this.mInflater = LayoutInflater.from(this.mContext);
}
public int findGatheredItem(Conversation.ConversationType type) {
int index = this.getCount();
int position = -1;
while (index-- > 0) {
UIConversation uiConversation = (UIConversation) this.getItem(index);
if (uiConversation.getConversationType().equals(type)) {
position = index;
break;
}
}
return position;
}
public int findPosition(Conversation.ConversationType type, String targetId) {
int index = this.getCount();
int position = -1;
while (index-- > 0) {
if (((UIConversation) this.getItem(index)).getConversationType().equals(type) && ((UIConversation) this.getItem(index)).getConversationTargetId().equals(targetId)) {
position = index;
break;
}
}
return position;
}
protected View newView(Context context, int position, ViewGroup group) {
View result = this.mInflater.inflate(R.layout.my_rc_item_conversation, (ViewGroup) null);
MyConversationListAdapter.ViewHolder holder = new MyConversationListAdapter.ViewHolder();
holder.layout = this.findViewById(result, R.id.rc_item_conversation);
holder.leftImageView = (AsyncImageView) this.findViewById(result, R.id.rc_left);
holder.contentView = (ProviderContainerView) this.findViewById(result, R.id.rc_content);
result.setTag(holder);
return result;
}
protected void bindView(View v, int position, final UIConversation data) {
MyConversationListAdapter.ViewHolder holder = (MyConversationListAdapter.ViewHolder) v.getTag();
if (data != null) {
IContainerItemProvider provider = RongContext.getInstance().getConversationTemplate(data.getConversationType().getName());
if (provider == null) {
LogUtil.i("MyConversationListAdapter----->bindView: provider is null");
} else {
View view = holder.contentView.inflate(provider);
provider.bindView(view, position, data);//内容view
if (data.isTop()) {
holder.layout.setBackgroundDrawable(this.mContext.getResources().getDrawable(R.drawable.rc_item_top_list_selector));
} else {
holder.layout.setBackgroundDrawable(this.mContext.getResources().getDrawable(R.drawable.rc_item_list_selector));
}
ConversationProviderTag tag = RongContext.getInstance().getConversationProviderTag(data.getConversationType().getName());
int defaultId;
if (data.getConversationType().equals(Conversation.ConversationType.GROUP)) {
defaultId = R.drawable.portrait_circle_holder;
} else if (data.getConversationType().equals(Conversation.ConversationType.DISCUSSION)) {
defaultId = R.drawable.portrait_circle_holder;
} else {
defaultId = R.drawable.portrait_circle_holder;
}
if (tag.portraitPosition() == 1) {
if (data.getConversationGatherState()) {
holder.leftImageView.setAvatar((String) null, defaultId);
} else if (data.getIconUrl() != null) {
holder.leftImageView.setAvatar(data.getIconUrl().toString(), defaultId);
} else {
holder.leftImageView.setAvatar((String) null, defaultId);
}
// data.getUnReadMessageCount()
}
MessageContent content = data.getMessageContent();
if (content != null && content.isDestruct()) {
RongIMClient.getInstance().getMessage(data.getLatestMessageId(), new RongIMClient.ResultCallback<Message>() {
public void onSuccess(Message message) {
if (message == null) {
EventBus.getDefault().post(new Event.MessageDeleteEvent(new int[]{data.getLatestMessageId()}));
} else if (message.getReadTime() > 0L) {
long readTime = message.getReadTime();
long serverTime = System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime();
long delay = message.getContent().getDestructTime() - (serverTime - readTime) / 1000L;
if (delay > 0L) {
RongIM.getInstance().createDestructionTask(message, (DestructionTaskManager.OnOverTimeChangeListener) null, ConversationListFragment.TAG);
} else {
EventBus.getDefault().post(new Event.DestructionEvent(message));
}
}
}
public void onError(RongIMClient.ErrorCode e) {
}
});
}
}
}
}
public void setOnPortraitItemClick(MyConversationListAdapter.OnPortraitItemClick onPortraitItemClick) {
this.mOnPortraitItemClick = onPortraitItemClick;
}
public interface OnPortraitItemClick {
void onPortraitItemClick(View var1, UIConversation var2);
boolean onPortraitItemLongClick(View var1, UIConversation var2);
}
protected class ViewHolder {
public View layout;
public AsyncImageView leftImageView;
public ProviderContainerView contentView;
protected ViewHolder() {
}
}
}
主要就是修改item的布局然后作相应处理,内容组件继续使用融云提供的,融云页提供了内容组件的自定义方式:自定义会话模板MyPrivateConversationProvider
@ConversationProviderTag(
conversationType = "private",
portraitPosition = 1
)
public class MyPrivateConversationProvider implements ConversationProvider<UIConversation> {
private static final String TAG = "MyPrivateConversationProvider";
public MyPrivateConversationProvider() {
}
public View newView(Context context, ViewGroup viewGroup) {
View result = LayoutInflater.from(context).inflate(R.layout.my_rc_item_base_conversation, (ViewGroup) null);
MyPrivateConversationProvider.ViewHolder holder = new MyPrivateConversationProvider.ViewHolder();
holder.title = (TextView) result.findViewById(R.id.rc_conversation_title);
holder.time = (TextView) result.findViewById(R.id.rc_conversation_time);
holder.content = (TextView) result.findViewById(R.id.rc_conversation_content);
holder.notificationBlockImage = (ImageView) result.findViewById(R.id.rc_conversation_msg_block);
holder.readStatus = (ImageView) result.findViewById(R.id.rc_conversation_status);
holder.unread = (NotifycationView) result.findViewById(R.id.unread_message);
result.setTag(holder);
return result;
}
private void handleMentionedContent(final MyPrivateConversationProvider.ViewHolder holder, final View view, final UIConversation data) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
final String preStr = view.getContext().getString(R.string.rc_message_content_mentioned);
if (holder.content.getWidth() > 60) {
CharSequence cutStr = TextUtils.ellipsize(preStr + " " + data.getConversationContent(), holder.content.getPaint(), (float) (holder.content.getWidth() - 60), TextUtils.TruncateAt.END);
SpannableString string = new SpannableString(cutStr);
string.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_mentioned_color)), 0, preStr.length(), 33);
builder.append(string);
AndroidEmoji.ensure(builder);
holder.content.setText(builder, TextView.BufferType.SPANNABLE);
} else {
holder.content.post(new Runnable() {
public void run() {
if (holder.content.getWidth() > 60) {
CharSequence cutStr = TextUtils.ellipsize(preStr + " " + data.getConversationContent(), holder.content.getPaint(), (float) (holder.content.getWidth() - 40), TextUtils.TruncateAt.END);
SpannableString strx = new SpannableString(cutStr);
strx.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_mentioned_color)), 0, preStr.length(), 33);
builder.append(strx);
} else {
SpannableString str = new SpannableString(preStr + " " + data.getConversationContent());
str.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_mentioned_color)), 0, preStr.length(), 33);
builder.append(str);
}
AndroidEmoji.ensure(builder);
holder.content.setText(builder, TextView.BufferType.SPANNABLE);
}
});
}
}
private void handleDraftContent(final MyPrivateConversationProvider.ViewHolder holder, final View view, final UIConversation data) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
final String preStr = view.getContext().getString(R.string.rc_message_content_draft);
if (holder.content.getWidth() > 60) {
CharSequence cutStr = TextUtils.ellipsize(preStr + " " + data.getDraft(), holder.content.getPaint(), (float) (holder.content.getWidth() - 60), TextUtils.TruncateAt.END);
SpannableString string = new SpannableString(cutStr);
string.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_draft_color)), 0, preStr.length(), 33);
builder.append(string);
AndroidEmoji.ensure(builder);
holder.content.setText(builder, TextView.BufferType.SPANNABLE);
} else {
holder.content.post(new Runnable() {
public void run() {
if (holder.content.getWidth() > 60) {
CharSequence cutStr = TextUtils.ellipsize(preStr + " " + data.getDraft(), holder.content.getPaint(), (float) (holder.content.getWidth() - 60), TextUtils.TruncateAt.END);
SpannableString strx = new SpannableString(cutStr);
strx.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_draft_color)), 0, preStr.length(), 33);
builder.append(strx);
} else {
SpannableString str = new SpannableString(preStr + " " + data.getDraft());
str.setSpan(new ForegroundColorSpan(view.getContext().getResources().getColor(R.color.rc_draft_color)), 0, preStr.length(), 33);
builder.append(str);
}
AndroidEmoji.ensure(builder);
holder.content.setText(builder, TextView.BufferType.SPANNABLE);
}
});
}
}
private void handleCommonContent(final MyPrivateConversationProvider.ViewHolder holder, UIConversation data) {
if (holder.content.getWidth() > 60 && data.getConversationContent() != null) {
CharSequence cutStr = TextUtils.ellipsize(data.getConversationContent(), holder.content.getPaint(), (float) (holder.content.getWidth() - 60), TextUtils.TruncateAt.END);
holder.content.setText(cutStr, TextView.BufferType.SPANNABLE);
} else {
final CharSequence cutStr = data.getConversationContent();
holder.content.post(new Runnable() {
public void run() {
if (holder.content.getWidth() > 60 && cutStr != null) {
CharSequence str = TextUtils.ellipsize(cutStr, holder.content.getPaint(), (float) (holder.content.getWidth() - 60), TextUtils.TruncateAt.END);
holder.content.setText(str, TextView.BufferType.SPANNABLE);
} else {
holder.content.setText(cutStr);
}
}
});
}
}
public void bindView(View view, int position, UIConversation data) {
MyPrivateConversationProvider.ViewHolder holder = (MyPrivateConversationProvider.ViewHolder) view.getTag();
ProviderTag tag = null;
if (data == null) {
holder.title.setText((CharSequence) null);
holder.time.setText((CharSequence) null);
holder.content.setText((CharSequence) null);
holder.unread.setVisibility(View.GONE);
} else {
holder.title.setText(data.getUIConversationTitle());
String time = RongDateUtils.getConversationListFormatDate(data.getUIConversationTime(), view.getContext());
holder.time.setText(time);
if (data.getUnReadMessageCount()>0) {
holder.unread.setVisibility(View.VISIBLE);
holder.unread.setNotifyCount(data.getUnReadMessageCount());
}else {
holder.unread.setVisibility(View.GONE);
}
if (TextUtils.isEmpty(data.getDraft()) && !data.getMentionedFlag()) {
boolean readRec = false;
try {
readRec = view.getResources().getBoolean(R.bool.rc_read_receipt);
} catch (Resources.NotFoundException var10) {
RLog.e("MyPrivateConversationProvider", "rc_read_receipt not configure in rc_config.xml");
var10.printStackTrace();
}
if (readRec) {
if (data.getSentStatus() == Message.SentStatus.READ && data.getConversationSenderId().equals(RongIM.getInstance().getCurrentUserId()) && !(data.getMessageContent() instanceof RecallNotificationMessage)) {
holder.readStatus.setVisibility(View.VISIBLE);
} else {
holder.readStatus.setVisibility(View.GONE);
}
}
this.handleCommonContent(holder, data);
} else {
if (data.getMentionedFlag()) {
this.handleMentionedContent(holder, view, data);
} else {
this.handleDraftContent(holder, view, data);
}
holder.readStatus.setVisibility(View.GONE);
}
if (RongContext.getInstance() != null && data.getMessageContent() != null) {
tag = RongContext.getInstance().getMessageProviderTag(data.getMessageContent().getClass());
}
if (data.getSentStatus() != null && (data.getSentStatus() == Message.SentStatus.FAILED || data.getSentStatus() == Message.SentStatus.SENDING) && tag != null && tag.showWarning() && data.getConversationSenderId() != null && data.getConversationSenderId().equals(RongIM.getInstance().getCurrentUserId())) {
Bitmap bitmap = BitmapFactory.decodeResource(view.getResources(), R.drawable.rc_conversation_list_msg_send_failure);
int width = bitmap.getWidth();
Drawable drawable = null;
if (data.getSentStatus() == Message.SentStatus.FAILED && TextUtils.isEmpty(data.getDraft())) {
drawable = view.getContext().getResources().getDrawable(R.drawable.rc_conversation_list_msg_send_failure);
} else if (data.getSentStatus() == Message.SentStatus.SENDING && TextUtils.isEmpty(data.getDraft())) {
drawable = view.getContext().getResources().getDrawable(R.drawable.rc_conversation_list_msg_sending);
}
if (drawable != null) {
drawable.setBounds(0, 0, width, width);
holder.content.setCompoundDrawablePadding(10);
holder.content.setCompoundDrawables(drawable, (Drawable) null, (Drawable) null, (Drawable) null);
}
} else {
holder.content.setCompoundDrawables((Drawable) null, (Drawable) null, (Drawable) null, (Drawable) null);
}
Conversation.ConversationNotificationStatus status = data.getNotificationStatus();
if (status != null && status.equals(Conversation.ConversationNotificationStatus.DO_NOT_DISTURB)) {
holder.notificationBlockImage.setVisibility(View.VISIBLE);
} else {
holder.notificationBlockImage.setVisibility(View.GONE);
}
}
}
public Spannable getSummary(UIConversation data) {
return null;
}
public String getTitle(String userId) {
UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(userId);
return userInfo == null ? userId : userInfo.getName();
}
public Uri getPortraitUri(String userId) {
UserInfo userInfo = RongUserInfoManager.getInstance().getUserInfo(userId);
return userInfo == null ? null : userInfo.getPortraitUri();
}
protected class ViewHolder {
public TextView title;
public TextView time;
public TextView content;
public ImageView notificationBlockImage;
public ImageView readStatus;
public NotifycationView unread;
protected ViewHolder() {
}
}
}
然后在Application里面注册会话模板
//注册会话模板
RongIM.getInstance().registerConversationTemplate(new MyPrivateConversationProvider());
2.消息自定义
融云提供了消息的自定义和会话一样需要自定义消息模板
@MessageTag(
value = "AH:SysMsg",
flag = 3
)
@DestructionTag
public class AntihivSystemMessage extends MessageContent {
public static final Creator<AntihivSystemMessage> CREATOR = new Creator<AntihivSystemMessage>() {
public AntihivSystemMessage createFromParcel(Parcel source) {
return new AntihivSystemMessage(source);
}
public AntihivSystemMessage[] newArray(int size) {
return new AntihivSystemMessage[size];
}
};
private String content;
protected String extra;
public AntihivSystemMessage(Parcel in) {
this.setExtra(ParcelUtils.readFromParcel(in));
this.setContent(ParcelUtils.readFromParcel(in));
this.setUserInfo((UserInfo) ParcelUtils.readFromParcel(in, UserInfo.class));
this.setMentionedInfo((MentionedInfo) ParcelUtils.readFromParcel(in, MentionedInfo.class));
this.setDestruct(ParcelUtils.readIntFromParcel(in) == 1);
this.setDestructTime(ParcelUtils.readLongFromParcel(in));
}
public void setContent(String content) {
this.content = content;
}
public void setExtra(String extra) {
this.extra = extra;
}
public String getContent() {
return content;
}
public String getExtra() {
return extra;
}
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
jsonObj.put("content", getEmotion(getContent()));
if (!TextUtils.isEmpty(getExtra())) {
jsonObj.put("extra", getExtra());
}
if (getJSONUserInfo() != null) {
jsonObj.putOpt("user", getJSONUserInfo());
}
if (getJsonMentionInfo() != null) {
jsonObj.putOpt("mentionedInfo", getJsonMentionInfo());
}
jsonObj.put("isBurnAfterRead", isDestruct());
jsonObj.put("burnDuration", getDestructTime());
} catch (JSONException var4) {
RLog.e("TextMessage", "JSONException " + var4.getMessage());
}
try {
return jsonObj.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException var3) {
RLog.e("TextMessage", "UnsupportedEncodingException ", var3);
return null;
}
}
public AntihivSystemMessage(byte[] data) {
String jsonStr = null;
try {
if (data != null && data.length >= 40960) {
RLog.e("TextMessage", "TextMessage length is larger than 40KB, length :" + data.length);
}
jsonStr = new String(data, "UTF-8");
} catch (UnsupportedEncodingException var5) {
RLog.e("TextMessage", "UnsupportedEncodingException ", var5);
}
try {
JSONObject jsonObj = new JSONObject(jsonStr);
if (jsonObj.has("content")) {
this.setContent(jsonObj.optString("content"));
}
if (jsonObj.has("extra")) {
this.setExtra(jsonObj.optString("extra"));
}
if (jsonObj.has("user")) {
this.setUserInfo(this.parseJsonToUserInfo(jsonObj.getJSONObject("user")));
}
if (jsonObj.has("mentionedInfo")) {
this.setMentionedInfo(this.parseJsonToMentionInfo(jsonObj.getJSONObject("mentionedInfo")));
}
if (jsonObj.has("isBurnAfterRead")) {
this.setDestruct(jsonObj.getBoolean("isBurnAfterRead"));
}
if (jsonObj.has("burnDuration")) {
this.setDestructTime(jsonObj.getLong("burnDuration"));
}
} catch (JSONException var4) {
RLog.e("TextMessage", "JSONException " + var4.getMessage());
}
}
private String getEmotion(String content) {
Pattern pattern = Pattern.compile("\\[/u([0-9A-Fa-f]+)\\]");
Matcher matcher = pattern.matcher(content);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
int inthex = Integer.parseInt(matcher.group(1), 16);
matcher.appendReplacement(sb, String.valueOf(Character.toChars(inthex)));
}
matcher.appendTail(sb);
return sb.toString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
ParcelUtils.writeToParcel(dest, this.getExtra());
ParcelUtils.writeToParcel(dest, this.content);
ParcelUtils.writeToParcel(dest, this.getUserInfo());
ParcelUtils.writeToParcel(dest, this.getMentionedInfo());
ParcelUtils.writeToParcel(dest, this.isDestruct() ? 1 : 0);
ParcelUtils.writeToParcel(dest, this.getDestructTime());
}
public List<String> getSearchableWord() {
List<String> words = new ArrayList();
words.add(this.content);
return words;
}
@Override
public String toString() {
return "AntihivSystemMessage{" +
"content='" + content + '\'' +
", extra='" + extra + '\'' +
'}';
}
}
同样需要在Application进行注册
这里我给出我写初始化代码
private void initIm() {
final String imToken = SharePreferenceUtil.getString(this, AppConstant.IM_TOKEN);
if (getApplicationInfo().packageName.equals(getCurProcessName(getApplicationContext()))) {
RongIM.init(this);//init Im
IMListener.init(this);
IMUserInfoManager.getInstance().openDB();
//注册该会话模板
RongIM.getInstance().registerConversationTemplate(new MyPrivateConversationProvider());
//注册自定义消息
RongIM.registerMessageType(AntihivSystemMessage.class);
RongIM.registerMessageType(DeleteFriendsMessage.class);
//注册消息模板
RongIM.getInstance().registerMessageTemplate(new AntihivSystemMessageProvider());
RongIM.setConnectionStatusListener(new RongIMClient.ConnectionStatusListener() {
@Override
public void onChanged(ConnectionStatus status) {
LogUtil.i("Application----->Rongyun onChanged: ---" + status);
if (status == ConnectionStatus.TOKEN_INCORRECT) {
if (!TextUtils.isEmpty(imToken)) {
RongIM.connect(imToken, IMListener.getInstance().getConnectCallback());
} else {
LogUtil.i("token is empty, can not reconnect");
}
}
}
});
}
}
其它
其它需要主页的地方就是如果想要修改一些配置比如@功能,回执等可以修改rc_config.xml这个文件。
使用了混淆的项目需要加上融云的混淆:
# ------融云混淆
-keepattributes Exceptions,InnerClasses
-keepattributes Signature
# RongCloud SDK
-keep class io.rong.** {*;}
-keep class cn.rongcloud.** {*;}
-keep class * implements io.rong.imlib.model.MessageContent {*;}
-dontwarn io.rong.push.**
-dontnote com.xiaomi.**
-dontnote com.google.android.gms.gcm.**
-dontnote io.rong.**
# VoIP
-keep class io.agora.rtc.** {*;}
# Location
-keep class com.amap.api.**{*;}
-keep class com.amap.api.services.**{*;}
# 红包
-keep class com.google.gson.** { *; }
-keep class com.uuhelper.Application.** {*;}
-keep class net.sourceforge.zbar.** { *; }
-keep class com.google.android.gms.** { *; }
-keep class com.alipay.** {*;}
-keep class com.jrmf360.rylib.** {*;}
-ignorewarnings
#融云EventBus需要以onEvent开头
-keepclassmembers class ** {
public void onEvent*(**);
}
# ------融云混淆 end
总结
做到以上几点基本上可以实现你所需要的即时通讯能力和页面效果了,如果对UI要求更高就需要多花心思在看融云官方api和自定义UI上面。