项目总结之即时通讯
融云的服务真是快,有问题提工单,工程师回答速度快,可以很方便解决实际开发中的问题。作为总结,我们还是随便聊聊融云的使用吧!直接进入正题————>>>
首先去融云开发者平台注册、下载SDK、导包、连接、实现会话列表和会话界面这里就简单说明一下。不懂的可以去之前文章看。本片重点说一下自定义消息和plugin扩展区域自定义。
第一步,使用第三方的东西肯定要去他们的官网注册一个开发者账号,步骤很简单,一步步填写,下一步就行。注意手机号的填写,还是要填写真实的,貌似有条规定,如果手机号为空号可能会导致他们不会给改用户下的app提供服务,所以为了安全起见还是填写真实的手机号码。注册链接地址。
第二步:下载SDK,建议SDK别使用当前太低版本、因为有些功能可能不支持。下载SDK时,我们也要把官方提供的demo下载下来,毕竟这是第一手资料,官网提供的demo功能还是比较丰富的,如果应用要求的功能不是很多,demo里面提供的基本功能使应该能满足的。demo可能是托管在github上的,所以还要有个git账号。请自行注册,毕竟开发者还是应该多去上面看看大牛写的开源东西来学习。
第三步:注册应用,使用第三方的工具,不注册怎么用呢?个人中心填写下应用的基本情况应该没啥问题了。
简要的几个步骤,具体使用可以参照官方提供的帮助文档,官方文档地址戳这里
功能介绍
前沿简介
前奏
使用第三方的库,首先就需要进行第三方要求的初始化操作,这部分代码,基本直接copy官网提供的就可以,这样可以保证准确性。所以这里简要介绍需要我们处理的几个点。
1、Manifest.xml文件中:
android:host选项的值全部为自己应用的包名。例如:android:host=”com.dsw.infor”
meta-data标签中的appkey的值要改为自己应用申请的appkey
2、在我们的Application中进行初始化,执行RongIM.init(this);
代码如下:
/**
* 应用启动时,判断用户是否已接受隐私协议,如果已接受,正常初始化;否则跳转到隐私授权页面请求用户授权。
*/
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
// if (!AppContext.isInitialized()) {
// AppContext.init(getApplicationContext());
// }
//用户已接受隐私协议,进行初始化
String appKey = "pvxdm17jpws8r";
// 第一个参数必须传应用上下文
RongIM.init(this.getApplicationContext(), appKey);
//注册Plugin自定义类型
registerExtensionPlugin();
//消息自定义类型
RongIM.registerMessageType(RedPackageMessage.class);
RongIM.registerMessageTemplate(new RedPackageItemProvider());
}
private void registerExtensionPlugin() {
List 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().unregisterExtensionModule(defaultModule);
RongExtensionManager.getInstance().registerExtensionModule(new SealExtensionModule());
}
}
}
}
3、初始化的工作完成后,然后我们就在我们成功登陆后,进行服务器的链接。此时,我们需要调用我们的后台来获取融云认证的token信息。测试阶段,我们可以使用api调试,手动生成来用。
RongIM.connect(token, new ConnectCallback() {
@Override
public void onSuccess(String arg0) {
Log.d("RongClound", "RongClound: Tocken Success");
}
@Override
public void onError(ErrorCode arg0) {
Log.d("RongClound", "RongClound: Tocken Error,ErrorCode:" + arg0);
}
@Override
public void onTokenIncorrect() {
Log.d("RongClound", "RongClound: onTokenIncorrect");
}
});
4.单聊功能
由于IMKit中融云提供的界面都是基于fragment,所以使用也是简单,我们只需要简单配置下我们的会话列表界面就可以了,比如:
布局搞好了,我们要创建一个ConversationActivity来进行显示。
public class ConversationListActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conversation_list);
ConversationListFragment conversationListFragment=new ConversationListFragment();
// 此处设置 Uri. 通过 appendQueryParameter 去设置所要支持的会话类型. 例如
// .appendQueryParameter(Conversation.ConversationType.PRIVATE.getName(),"false")
// 表示支持单聊会话, false 表示不聚合显示, true 则为聚合显示
Uri uri = Uri.parse("rong://" +
getApplicationContext().getApplicationInfo().packageName).buildUpon()
.appendPath("conversationlist")
.appendQueryParameter(Conversation.ConversationType.PRIVATE.getName(), "false") //设置私聊会话是否聚合显示
.appendQueryParameter(Conversation.ConversationType.GROUP.getName(), "false")//群组
.appendQueryParameter(Conversation.ConversationType.PUBLIC_SERVICE.getName(), "false")//公共服务号
.appendQueryParameter(Conversation.ConversationType.APP_PUBLIC_SERVICE.getName(), "false")//订阅号
.appendQueryParameter(Conversation.ConversationType.SYSTEM.getName(), "true")//系统
.build();
conversationListFragment.setUri(uri);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.container, conversationListFragment);
transaction.commit();
Activity创建好自然要在Manifest中进行声明
回话界面
public class ConversationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conversation);
ConversationFragment conversationFragment = new ConversationFragment();
Intent intent = getIntent(); // 取得从上一个Activity当中传递过来的Intent对象
String targetId = null;
if (intent != null) {
targetId = intent.getStringExtra("targetId");
}
Uri uri = Uri.parse("rong://" +
getApplicationContext().getApplicationInfo().packageName).buildUpon()
.appendPath("conversation").appendPath("private")
.appendQueryParameter("targetId", targetId)//设置私聊会话是否聚合显示,targetId:单聊人的id
.build();
conversationFragment.setUri(uri);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.container, conversationFragment);
transaction.commit();
}
}
至此单聊功能就实现了!开始进入正题自定义消息和Plugin扩展。
自定义消息
参考资料:http://www.rongcloud.cn/docs/android.html#新建消息
- 自定义消息实体 –RedPackageMessage(一个自定义的红包消息)
package com.example.mydemoplugin.PhoneInfoProvider;
/**
* Created by zhangbowen on 6/15/21.
**/
import android.os.Parcel;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import io.rong.common.ParcelUtils;
import io.rong.imlib.MessageTag;
import io.rong.imlib.model.MessageContent;
import io.rong.message.CSSuspendMessage;
/*
* 注解名:MessageTag ;属性:value ,flag; value 即 ObjectName 是消息的唯一标识不可以重复,
* 开发者命名时不能以 RC 开头,避免和融云内置消息冲突;flag 是用来定义消息的可操作状态。
*如下面代码段,自定义消息名称 CustomizeMessage ,vaule 是 app:custom ,
* flag 是 MessageTag.ISCOUNTED | MessageTag.ISPERSISTED 表示消息计数且存库。
* app:RedPkgMsg: 这是自定义消息类型的名称,测试的时候用"app:RedPkgMsg";
* */
@MessageTag(value = "app:RedPkgMsg", flag = MessageTag.ISCOUNTED | MessageTag.ISPERSISTED)
public class RedPackageMessage extends MessageContent {
//自定义的属性
private String title;
private String storeName;
private String desc1;
private String desc2;
public RedPackageMessage() {
}
public static RedPackageMessage obtain(String title, String storeName, String desc1, String desc2) {
RedPackageMessage message = new RedPackageMessage();
message.title = title;
message.storeName = storeName;
message.desc1 = desc1;
message.desc2 = desc2;
return message;
}
/*
*
* 实现 encode() 方法,该方法的功能是将消息属性封装成 json 串,
* 再将 json 串转成 byte 数组,该方法会在发消息时调用,如下面示例代码:
* */
@Override
public byte[] encode() {
JSONObject jsonObj = new JSONObject();
try {
jsonObj.put("title", this.getTitle());
jsonObj.put("storeName", this.getStoreName());
jsonObj.put("desc1", this.getDesc1());
jsonObj.put("desc2", this.getDesc2());
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
try {
return jsonObj.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/*
* 覆盖父类的 MessageContent(byte[] data) 构造方法,该方法将对收到的消息进行解析,
* 先由 byte 转成 json 字符串,再将 json 中内容取出赋值给消息属性。
* */
public RedPackageMessage(byte[] data) {
String jsonStr = null;
try {
jsonStr = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
JSONObject jsonObj = new JSONObject(jsonStr);
if (jsonObj.has("title"))
setTitle(jsonObj.optString("title"));
if (jsonObj.has("storeName"))
setStoreName(jsonObj.optString("storeName"));
if (jsonObj.has("desc1"))
setDesc1(jsonObj.optString("desc1"));
if (jsonObj.has("desc2"))
setDesc2(jsonObj.optString("desc2"));
} catch (JSONException e) {
Log.d("JSONException", e.getMessage());
}
}
//给消息赋值。
public RedPackageMessage(Parcel in) {
setTitle(ParcelUtils.readFromParcel(in));//该类为工具类,消息属性
//这里可继续增加你消息的属性
setStoreName(ParcelUtils.readFromParcel(in));//该类为工具类,消息属性
setDesc1(ParcelUtils.readFromParcel(in));//该类为工具类,消息属性
setDesc2(ParcelUtils.readFromParcel(in));//该类为工具类,消息属性
}
/**
* 读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。
*/
public static final Creator CREATOR = new Creator() {
@Override
public RedPackageMessage createFromParcel(Parcel source) {
return new RedPackageMessage(source);
}
@Override
public RedPackageMessage[] newArray(int size) {
return new RedPackageMessage[size];
}
};
@Override
public int describeContents() {
return 0;
}
/**
* 将类的数据写入外部提供的 Parcel 中。
*
* @param dest 对象被写入的 Parcel。
* @param flags 对象如何被写入的附加标志。
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
ParcelUtils.writeToParcel(dest, getTitle());
ParcelUtils.writeToParcel(dest, getStoreName());
ParcelUtils.writeToParcel(dest, getDesc1());
ParcelUtils.writeToParcel(dest, getDesc2());
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public String getDesc2() {
return desc2;
}
public void setDesc2(String desc2) {
this.desc2 = desc2;
}
}
2.自定义消息提供者
package com.example.mydemoplugin.PhoneInfoProvider;
import android.content.ClipboardManager;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.example.mydemoplugin.R;
import io.rong.imkit.RongIM;
import io.rong.imkit.emoticon.AndroidEmoji;
import io.rong.imkit.model.ProviderTag;
import io.rong.imkit.model.UIMessage;
import io.rong.imkit.utilities.OptionsPopupDialog;
import io.rong.imkit.widget.provider.IContainerItemProvider;
import io.rong.imlib.RongIMClient;
import io.rong.imlib.model.Message;
/**
* Created by zhangbowen on 6/15/21.PhoneInfo的布局
* desc新建一个消息类继承 IContainerItemProvider.MessageProvider 类,实现对应接口方法,
* 1.注意开头的注解!
* 2.注意泛型!
*/
@ProviderTag(
messageContent = RedPackageMessage.class,
showReadState = true
)
public class RedPackageItemProvider extends IContainerItemProvider.MessageProvider {
public RedPackageItemProvider() {
}
@Override
public View newView(Context context, ViewGroup viewGroup) {
//这就是展示在会话界面的自定义的消息的布局
View view = LayoutInflater.from(context).inflate(R.layout.item_redpackage_message, null);
ViewHolder holder = new ViewHolder();
holder.ll_msg = (FrameLayout) view.findViewById(R.id.ll_msg);
holder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
holder.tvStoreName = (TextView) view.findViewById(R.id.tv_store_name);
holder.tvDesc1 = (TextView) view.findViewById(R.id.tv_desc1);
holder.tvDesc2 = (TextView) view.findViewById(R.id.tv_desc2);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, int i, RedPackageMessage redPackageMessage, UIMessage message) {
//根据需求,适配数据
ViewHolder holder = (ViewHolder) view.getTag();
if (message.getMessageDirection() == Message.MessageDirection.SEND) {//消息方向,自己发送的
// holder.ll_msg.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_right);
} else {
// holder.ll_msg.setBackgroundResource(io.rong.imkit.R.drawable.rc_ic_bubble_left);
}
// AndroidEmoji.ensure((Spannable) holder.message.getText());//显示消息中的 Emoji 表情。
holder.tvTitle.setText(redPackageMessage.getTitle());
holder.tvStoreName.setText(redPackageMessage.getStoreName());
holder.tvDesc1.setText(redPackageMessage.getDesc1());
holder.tvDesc2.setText(redPackageMessage.getDesc2());
}
@Override
public Spannable getContentSummary(RedPackageMessage redPackageMessage) {
return new SpannableString(redPackageMessage.getDesc1());
}
@Override
public void onItemClick(View view, int i, RedPackageMessage redPackageMessage, UIMessage uiMessage) {
}
@Override
public void onItemLongClick(View view, int i, RedPackageMessage redPackageMessage, UIMessage uiMessage) {
//实现长按删除等功能,咱们直接复制融云其他provider的实现
String[] items1;//复制,删除
items1 = new String[]{view.getContext().getResources().getString(io.rong.imkit.R.string.rc_dialog_item_message_copy), view.getContext().getResources().getString(io.rong.imkit.R.string.rc_dialog_item_message_delete)};
OptionsPopupDialog.newInstance(view.getContext(), items1).setOptionsPopupDialogListener(new OptionsPopupDialog.OnOptionsItemClickedListener() {
public void onOptionsItemClicked(int which) {
// Log.e("实现长按删除等功能",uiMessage.getMessage().getMessageId()+"---"+redPackageMessage.describeContents()+"-----"+uiMessage.getMessage());
//长安复制暂时没有实现
if (which == 0) {
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
// clipboard.setText(redPackageMessage.encode());//这里是自定义消息的消息属性
} else if (which == 1) {
RongIM.getInstance().deleteMessages(new int[]{uiMessage.getMessage().getMessageId()}, (RongIMClient.ResultCallback) null);
}
}
}).show();
}
private static class ViewHolder {
TextView tvTitle, tvStoreName, tvDesc1, tvDesc2;
FrameLayout ll_msg;
}
}
3.注意自定义消息的布局:(整体布局外面再嵌套一层)
4.注册消息类型以及消息提供者
RongIM.init(this);
//注意,要在初始化之后注册
RongIM.registerMessageType(RedPackageMessage.class);
RongIM.registerMessageTemplate(new RedPackageItemProvider());
5.官网测试发消息
6.自定义红包Plugin
/**
* Created by zhangbowen on 6/15/21.
* 自定义红包Plugin
**/
public class MyPlugin implements IPluginModule {
@Override
public Drawable obtainDrawable(Context context) {
return ContextCompat.getDrawable(context, io.rong.imkit.R.drawable.rc_cs_evaluate_selector);
}
@Override
public String obtainTitle(Context context) {
return "红包";
}
@Override
public void onClick(Fragment fragment, RongExtension rongExtension) {
Log.e("发红包啦!!!", rongExtension.getTargetId());
RedPackageMessage redPackageMessage = RedPackageMessage.obtain("测试" + new Random().nextInt(1000), "商店名称" + new Random().nextInt(1000), "描述" + new Random().nextInt(1000), "描述" + new Random().nextInt(1000));
RongIM.getInstance().sendMessage(Conversation.ConversationType.PRIVATE, rongExtension.getTargetId(), redPackageMessage, "测试一下pushContent" + new Random().nextInt(100), "测试一下pushData" + new Random().nextInt(100)
, new IRongCallback.ISendMediaMessageCallback() {
@Override
public void onAttached(Message message) {
}
@Override
public void onSuccess(Message message) {
}
@Override
public void onError(Message message, RongIMClient.ErrorCode errorCode) {
}
@Override
public void onProgress(Message message, int i) {
}
@Override
public void onCanceled(Message message) {
}
});
}
@Override
public void onActivityResult(int i, int i1, Intent intent) {
Log.e("发红包啦!!!", "onActivityResult");
}
}
/**
* Created by zhangbowen on 6/15/21.
* Plugin自定义类型
**/
public class SealExtensionModule extends DefaultExtensionModule {
@Override
public List getPluginModules(Conversation.ConversationType conversationType) {
List pluginModuleList = new ArrayList<>();
IPluginModule image = new ImagePlugin();
IPluginModule location = new DefaultLocationPlugin();
// IPluginModule audio = new AudioPlugin();
// IPluginModule video = new VideoPlugin();
IPluginModule file = new FilePlugin();
IPluginModule myPlugin = new MyPlugin();
if (conversationType.equals(Conversation.ConversationType.GROUP) ||
conversationType.equals(Conversation.ConversationType.DISCUSSION) ||
conversationType.equals(Conversation.ConversationType.PRIVATE)) {
pluginModuleList.add(image);
pluginModuleList.add(location);
// pluginModuleList.add(audio);
// pluginModuleList.add(video);
pluginModuleList.add(file);
pluginModuleList.add(myPlugin);
} else {
pluginModuleList.add(image);
}
return pluginModuleList;
}
@Override
public List getEmoticonTabs() {
return super.getEmoticonTabs();
}
}
7.将自定义好的Plugin重新注册(在init初始化之后注册)
private void registerExtensionPlugin() {
List 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().unregisterExtensionModule(defaultModule);
RongExtensionManager.getInstance().registerExtensionModule(new SealExtensionModule());
}
}
}
以上就是关于融云在项目中的使用,融云提供很多功能,希望以后有机会再深入研究吧!
简单demo下载地址