前言
就目前市面的項(xiàng)目而已旨枯,即時(shí)通訊功能算是很普遍的需求深受boos喜愛(ài),boos最喜歡讓你擼出一個(gè)微信聊天來(lái)。當(dāng)然大多說(shuō)公司不會(huì)自己去搭建一個(gè)即時(shí)通訊技術(shù)棧癞志。更多的是集成第三方功能啥么,站在巨人的肩膀上快速開(kāi)發(fā)出boos要求的社交功能登舞。讀完本文你可以在3個(gè)小時(shí)內(nèi)作出一個(gè)心儀的IM功能(這個(gè)當(dāng)然是對(duì)于有一定開(kāi)發(fā)能力的讀者而言,初級(jí)碼仔先搬個(gè)小板凳慢慢學(xué)哦)...
當(dāng)然,接入融云的即時(shí)通訊功能最好是先看一遍官方文檔 https://www.rongcloud.cn/docs/ (這么做可以整體了解集成過(guò)程悬荣,做到心里有數(shù))菠秒。融云本身有為我們開(kāi)發(fā)者設(shè)計(jì)了界面組件庫(kù),可以極大地減少我們的開(kāi)發(fā)時(shí)間氯迂,在這里我比較建議創(chuàng)業(yè)型公司的同學(xué)最好先使用他們提供的頁(yè)面組件践叠,后面研究的比較透徹后可以自己去自定義UI.本文主要是對(duì)于自己項(xiàng)目中使用的功能做一個(gè)總結(jié),筆者只是小廠(chǎng)碼農(nóng)嚼蚀,也沒(méi)有去自己寫(xiě)UI禁灼,用的是改造官方的,所以大神們可以忽略本文轿曙。
我先屢一下大綱
1.注冊(cè)賬號(hào)弄捕,配置應(yīng)用信息
首先,在融云官網(wǎng)注冊(cè)https://www.rongcloud.cn/ 然后創(chuàng)建應(yīng)用导帝,獲取appkey(這個(gè)后面的sdk接入會(huì)用到)察藐。這些細(xì)節(jié)我就跳過(guò)了。
2.集成融云SDK
1.首先舟扎,在融云下載SDK(下載地址:https://www.rongcloud.cn/downloads)我這里只下載了IMLib分飞,IMKit前者是通訊能力庫(kù)是必須要的,后者是界面組件庫(kù)睹限,我們可以在它的基礎(chǔ)上快速開(kāi)發(fā)譬猫。如果你想自己寫(xiě)UI可以不要它。如果還想接入音視頻和紅包等功能還可以引入CallLib羡疗,RedPacket等庫(kù)染服,具體怎么選擇就看你的項(xiàng)目需求了。我這里只使用了IMLib和IMKit下文也是基于這兩者的叨恨。
2.根據(jù)官方指導(dǎo)采用導(dǎo)入Module的方式柳刮,AS中File -> New -> Import Module然后導(dǎo)入IMLib,IMKit。然后在settings.gradle文件中
include ':IMKit',':IMLib'
IMKit里面已經(jīng)引入IMLib所以我們只需要把IMKit進(jìn)行引入秉颗,在你使用的主項(xiàng)目的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="您的應(yīng)用 AppKey" />
在你的應(yīng)用的主項(xiàng)目 App Module 的 AndroidManifest.xml 文件中,添加 FileProvider 相關(guān)配置蚕甥,修改 android:authorities 為您的應(yīng)用的 “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有使用多進(jìn)程所以我建議這樣寫(xiě)
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,連接服務(wù)器
token需要后端去融云獲取菇怀,我們測(cè)試可以直接拷貝過(guò)去凭舶,實(shí)際使用中不建議這么做,我這里引入融云的原話(huà):
為了方便您在集成和測(cè)試過(guò)程中使用爱沟,我們還提供了 API 調(diào)試工具帅霜,在您不能部署服務(wù)器程序時(shí),可以直接通過(guò)傳入 userId 和 name 來(lái)獲得 Token呼伸。請(qǐng)?jiān)L問(wèn)融云開(kāi)發(fā)者平臺(tái)身冀,打開(kāi)您想測(cè)試的應(yīng)用,在左側(cè)菜單中選擇“API 調(diào)試”即可蜂大。
有了token之后可以連接服務(wù)器
private void connect(String token) {
if (getApplicationInfo().packageName.equals(App.getCurProcessName(getApplicationContext()))) {
RongIM.connect(token, new RongIMClient.ConnectCallback() {
/**
* Token 錯(cuò)誤闽铐。可以從下面兩點(diǎn)檢查 1. Token 是否過(guò)期奶浦,如果過(guò)期您需要向 App Server 重新請(qǐng)求一個(gè)新的 Token
* 2. token 對(duì)應(yīng)的 appKey 和工程里設(shè)置的 appKey 是否一致
*/
@Override
public void onTokenIncorrect() {
}
/**
* 連接融云成功
* @param userid 當(dāng)前 token 對(duì)應(yīng)的用戶(hù) id
*/
@Override
public void onSuccess(String userid) {
Log.d("LoginActivity", "--onSuccess" + userid);
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
}
/**
* 連接融云失敗
* @param errorCode 錯(cuò)誤碼兄墅,可到官網(wǎng) 查看錯(cuò)誤碼對(duì)應(yīng)的注釋
*/
@Override
public void onError(RongIMClient.ErrorCode errorCode) {
}
});
}
}
處理回調(diào),廣播接收器
我這里是把融云的回調(diào)統(tǒng)一放在類(lèi)里面進(jìn)行處理
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);//設(shè)置會(huì)話(huà)界面操作的監(jiān)聽(tīng)器澳叉。
RongIM.setConversationListBehaviorListener(this);
RongIM.setConnectionStatusListener(this);
RongIM.setUserInfoProvider(this, true);
RongIM.setGroupInfoProvider(this, true);
// RongIM.setLocationProvider(this);//設(shè)置地理位置提供者,不用位置的同學(xué)可以注掉此行代碼
RongIM.setOnReceiveMessageListener(this);//重點(diǎn)收到消息的回調(diào)
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);
}
/**
* 設(shè)置輸入面板
*/
private void setInputProvider() {
// 沒(méi)有特殊需求就忽略
// 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 錯(cuò)誤
LogUtil.i("ImService----->onTokenIncorrect: 融云token錯(cuò)誤");
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;
}
/****************** 融云回調(diào) ******************/
/*--------------- 消息交互 --------------*/
@Override
public boolean onUserPortraitClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo) {
//點(diǎn)擊頭像 這里群組默認(rèn)都是專(zhuān)家
if (conversationType == Conversation.ConversationType.CUSTOMER_SERVICE || conversationType == Conversation.ConversationType.PUBLIC_SERVICE || conversationType == Conversation.ConversationType.APP_PUBLIC_SERVICE) {
return false;
}
//開(kāi)發(fā)測(cè)試時(shí),發(fā)送系統(tǒng)消息的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;
}
/*--------------- 消息交互 --------------*/
/*--------------- 會(huì)話(huà)的交互 --------------*/
@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;
}
/*--------------- 會(huì)話(huà)的交互 --------------*/
@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")) {
//對(duì)方發(fā)來(lái)好友邀請(qǐng)
} else if (contactNotificationMessage.getOperation().equals("AcceptResponse")) {
//對(duì)方同意我的好友請(qǐng)求
}
} 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")) {
//創(chuàng)建群組
// 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) {
//獲取群成員 數(shù)據(jù)庫(kù)+網(wǎng)絡(luò)
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);
}
});
}
}
我這里只處理了單聊和群聊兩種情況隙咸,具體其它的應(yīng)用情景筆者在這里不再給出,讀者可以自己看融云文檔根據(jù)具體使用情景進(jìn)行功能補(bǔ)充
自定義廣播接收器
public class IMNotificationReceiver extends PushMessageReceiver {
@Override
public boolean onNotificationMessageArrived(Context context, PushType pushType, PushNotificationMessage message) {
return false; // 返回 false, 會(huì)彈出融云 SDK 默認(rèn)通知; 返回 true, 融云 SDK 不會(huì)彈通知, 通知需要由您自定義成洗。
}
@Override
public boolean onNotificationMessageClicked(Context context, PushType pushType, PushNotificationMessage message) {
return false;// 返回 false, 會(huì)走融云 SDK 默認(rèn)處理邏輯, 即點(diǎn)擊該通知會(huì)打開(kāi)會(huì)話(huà)列表或會(huì)話(huà)界面; 返回 true, 則由您自定義處理邏輯五督。
}
@Override
public void onThirdPartyPushState(PushType pushType, String action, long resultCode) {
super.onThirdPartyPushState(pushType, action, resultCode);
}
}
4.在頁(yè)面組件庫(kù)的基礎(chǔ)上自定義UI
1.會(huì)話(huà)列表自定義
可以根據(jù)IMKit里面的ConversationListFragment修改成自己的MyConversationListFragment,其實(shí)我只是把里面的適配器重寫(xiě)了MyConversationListAdapter所以這里重點(diǎn)介紹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);//內(nèi)容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的布局然后作相應(yīng)處理瓶殃,內(nèi)容組件繼續(xù)使用融云提供的充包,融云頁(yè)提供了內(nèi)容組件的自定義方式:自定義會(huì)話(huà)模板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里面注冊(cè)會(huì)話(huà)模板
//注冊(cè)會(huì)話(huà)模板
RongIM.getInstance().registerConversationTemplate(new MyPrivateConversationProvider());
2.消息自定義
融云提供了消息的自定義和會(huì)話(huà)一樣需要自定義消息模板
@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進(jìn)行注冊(cè)
這里我給出我寫(xiě)初始化代碼
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();
//注冊(cè)該會(huì)話(huà)模板
RongIM.getInstance().registerConversationTemplate(new MyPrivateConversationProvider());
//注冊(cè)自定義消息
RongIM.registerMessageType(AntihivSystemMessage.class);
RongIM.registerMessageType(DeleteFriendsMessage.class);
//注冊(cè)消息模板
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");
}
}
}
});
}
}
其它
其它需要主頁(yè)的地方就是如果想要修改一些配置比如@功能,回執(zhí)等可以修改rc_config.xml這個(gè)文件遥椿。
使用了混淆的項(xiàng)目需要加上融云的混淆:
# ------融云混淆
-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開(kāi)頭
-keepclassmembers class ** {
public void onEvent*(**);
}
# ------融云混淆 end
總結(jié)
做到以上幾點(diǎn)基本上可以實(shí)現(xiàn)你所需要的即時(shí)通訊能力和頁(yè)面效果了基矮,如果對(duì)UI要求更高就需要多花心思在看融云官方api和自定義UI上面。