项目中用到了融云IM聊天功能和自定义消息,今天记录一下接入的过程。
先看下效果图~
打开融云官网:https://www.rongcloud.cn/?注册账号登录后进入控制台找到服务管理然后创建自己的应用完成后就可以看到对应的Key。
进入开发者中心下载对应的SDK我这里选择的是手动集成方式,你也可以选择Maven集成。
我这里只保留了即时通讯组件 然后新建一个项目,把下载的SDK导入进来。?
在IMKIt build.gradle中把IMLib这个库依赖进去
api project(path: ':IMLib')
?在自己App build.gradle中把IMKit依赖进来
implementation project(path: ':IMKit')
另外用到的其他第三方库如下
implementation 'com.github.bumptech.glide:glide:4.13.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'
implementation 'com.makeramen:roundedimageview:2.3.0'
分别用来加载网络图片和设置圆角ImageView
现在到融云控制台去获取两个用户Token等下聊天时需要用,获取地址https://developer.rongcloud.cn/apitool/zDymWQBfv9jYwNFYMdQmWw
提交后就可以看到生成的Token
接着就是代码部分
MyApplication这个类用来初始化融云和注册自定义聊天界面,记得在AndroidManifest.xml清单文件中注册。
package com.ranlegeran.rongimtest.application;
import android.app.Application;
import com.ranlegeran.rongimtest.MyConversationActivity;
import io.rong.imkit.RongIM;
import io.rong.imkit.utils.RouteUtils;
/**
* 作者 :然了个然
* 简介 :一言以蔽之
* 时间 :2022/4/20 12:24
* 描述 :MyApplication
* 版本 :1.0
*/
public class MyApplication extends Application {
private static final String APP_KEY = "请填写你自己申请的Key"; //融云申请的Key
@Override
public void onCreate() {
super.onCreate();
//初始化融云
//第一个参数上下文,第二个申请的KEY,第三个是否开启推送 默认为开启
RongIM.init(this,APP_KEY,false);
//注册融云聊天界面
RouteUtils.registerActivity(RouteUtils.RongActivityType.ConversationActivity, MyConversationActivity.class);
}
}
activity_main.xml布局中只放置了一个Button用来点击连接融云服务器
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="点击连接"
android:textSize="15sp" />
</LinearLayout>
MainActivity中分别放置了两个Token用于测试聊天对话,在setOnClickListener点击事件中调用了连接融云服务器方法,连接成功后会返回当前的用户ID,接着通过Bundle把聊天会话的ID和商品信息传递到了聊天界面
package com.ranlegeran.rongimtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import io.rong.imkit.RongIM;
import io.rong.imkit.utils.RouteUtils;
import io.rong.imlib.RongIMClient;
import io.rong.imlib.model.Conversation;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button mBtnConnect;
//生成的用户Token
private static final String USER_TOKEN = "QgH4sdChZS4rgY6xYgZS9odPVJ5gESMYi3rwX5hZJmk=@log7.cn.rongnav.com;log7.cn.rongcfg.com";
//private static final String USER_TOKEN = "2WuaLMi7S1YrgY6xYgZS9iJkLNvgsH3JyOyhqYfp9dE=@log7.cn.rongnav.com;log7.cn.rongcfg.com";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnConnect = this.findViewById(R.id.btn_connect);
mBtnConnect.setOnClickListener(view -> {
RongIM.connect(USER_TOKEN, 0, new RongIMClient.ConnectCallback() {
@Override
public void onSuccess(String s) {
//连接融云成功后返回当前UserId
Toast.makeText(MainActivity.this, "连接成功用户ID" + s, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onSuccess: " + s);
//跳转到聊天界面
String mTargetId = "10002"; //对话的用户ID 生成的第二个Token
//模拟的商品数据,开发中以服务器返回的为准
String mGoodsCover = "https://s5.mogucdn.com/mlcdn/c45406/211005_0hi6k1e67flcc0cdh4dae2ll0lk0l_640x960.jpg_750x1000.v1cAC.81.jpg";
String mGoodsName = "全棉纯棉斜纹128684件套床品四件套床单被子套被罩床笠家用";
String mGoodsPrice = "¥107.00";
String mGoodsAddress = "浙江杭州";
String mGoodsTime = "2022-04-18";
//把商品的信息传递到聊天会话界面
Bundle mBundle = new Bundle();
mBundle.putString(Constants.KEY_TARGET_ID, mTargetId);
mBundle.putString(Constants.KEY_GOODS_COVER, mGoodsCover);
mBundle.putString(Constants.KEY_GOODS_NAME, mGoodsName);
mBundle.putString(Constants.KEY_GOODS_PRICE, mGoodsPrice);
mBundle.putString(Constants.KEY_GOODS_ADDRESS, mGoodsAddress);
mBundle.putString(Constants.KEY_GOODS_TIME, mGoodsTime);
RouteUtils.routeToConversationActivity(MainActivity.this, Conversation.ConversationType.PRIVATE,mTargetId,mBundle);
finish();
}
@Override
public void onError(RongIMClient.ConnectionErrorCode e) {
//连接融云失败
Log.e(TAG, "onError: " + e);
}
@Override
public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus code) {
//本地数据库打开状态回调
}
});
});
}
}
Constants这个类定义了一些参数如下
package com.ranlegeran.rongimtest;
/**
* 作者 :然了个然
* 简介 :一言以蔽之
* 时间 :2022/4/20 13:47
* 描述 :Constants
* 版本 :1.0
*/
public class Constants {
//聊天对话ID
public static final String KEY_TARGET_ID = "key_target_id";
//商品图片
public static final String KEY_GOODS_COVER = "key_goods_cover";
//商品名称
public static final String KEY_GOODS_NAME = "key_goods_name";
//商品价格
public static final String KEY_GOODS_PRICE = "key_goods_price";
//商品地址
public static final String KEY_GOODS_ADDRESS = "key_goods_address";
//商品时间
public static final String KEY_GOODS_TIME = "key_goods_time";
}
GoodsInfoRongMessage这个类是自定义消息继承融云MessageContent
encode方法把消息封装成json接着再转为byte
package com.ranlegeran.rongimtest.message;
import android.os.Parcel;
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;
/**
* 作者 :然了个然
* 简介 :一言以蔽之
* 时间 :2022/4/20 14:03
* 描述 :自定义融云消息体发送商品消息
* 版本 :1.0
*/
//消息唯一标识
@MessageTag(value = "app:custom_goods_info", flag = MessageTag.ISCOUNTED | MessageTag.ISPERSISTED)
public class GoodsInfoRongMessage extends MessageContent {
private String mGoodsCover; //商品图片
private String mGoodsName; //商品名称
private String mGoodsPrice; //商品价格
private String mGoodsAddress; //商品地址
private String mGoodsTime; //商品发布时间
private GoodsInfoRongMessage mGoodsInfoRongMessage;
public GoodsInfoRongMessage getGoodsInfoRongMessage() {
return mGoodsInfoRongMessage;
}
public void setGoodsInfoRongMessage(GoodsInfoRongMessage mGoodsInfoRongMessage) {
this.mGoodsInfoRongMessage = mGoodsInfoRongMessage;
}
public String getGoodsCover() {
return mGoodsCover;
}
public void setGoodsCover(String mGoodsCover) {
this.mGoodsCover = mGoodsCover;
}
public String getGoodsName() {
return mGoodsName;
}
public void setGoodsName(String mGoodsName) {
this.mGoodsName = mGoodsName;
}
public String getGoodsPrice() {
return mGoodsPrice;
}
public void setGoodsPrice(String mGoodsPrice) {
this.mGoodsPrice = mGoodsPrice;
}
public String getGoodsAddress() {
return mGoodsAddress;
}
public void setGoodsAddress(String mGoodsAddress) {
this.mGoodsAddress = mGoodsAddress;
}
public String getGoodsTime() {
return mGoodsTime;
}
public void setGoodsTime(String mGoodsTime) {
this.mGoodsTime = mGoodsTime;
}
/**
* 将本地消息对象序列化为消息数据
* @return 消息数据
*/
@Override
public byte[] encode() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("mGoodsCover", mGoodsCover);
jsonObject.put("mGoodsName", mGoodsName);
jsonObject.put("mGoodsPrice", mGoodsPrice);
jsonObject.put("mGoodsAddress", mGoodsAddress);
jsonObject.put("mGoodsTime", mGoodsTime);
try {
return jsonObject.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
public GoodsInfoRongMessage() {
}
public GoodsInfoRongMessage(byte[] data) {
super(data);
String jsonStr = null;
try {
jsonStr = new String(data,"UTF-8");
JSONObject objectRoot = new JSONObject(jsonStr);
setGoodsCover(objectRoot.getString("mGoodsCover"));
setGoodsName(objectRoot.getString("mGoodsName"));
setGoodsPrice(objectRoot.getString("mGoodsPrice"));
setGoodsAddress(objectRoot.getString("mGoodsAddress"));
setGoodsTime(objectRoot.getString("mGoodsTime"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static final Creator<GoodsInfoRongMessage> CREATOR = new Creator<GoodsInfoRongMessage>() {
@Override
public GoodsInfoRongMessage createFromParcel(Parcel source) {
return new GoodsInfoRongMessage(source);
}
@Override
public GoodsInfoRongMessage[] newArray(int size) {
return new GoodsInfoRongMessage[size];
}
};
@Override
public int describeContents() {
return 0;
}
/**
* 对消息属性进行序列化,将类的数据写入外部提供的Parcel中
* @param parcel
* @param flags
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
ParcelUtils.writeToParcel(parcel, mGoodsCover);
ParcelUtils.writeToParcel(parcel, mGoodsName);
ParcelUtils.writeToParcel(parcel, mGoodsPrice);
ParcelUtils.writeToParcel(parcel, mGoodsAddress);
ParcelUtils.writeToParcel(parcel, mGoodsTime);
}
public GoodsInfoRongMessage(Parcel parcel) {
mGoodsCover = ParcelUtils.readFromParcel(parcel);
mGoodsName = ParcelUtils.readFromParcel(parcel);
mGoodsPrice = ParcelUtils.readFromParcel(parcel);
mGoodsAddress = ParcelUtils.readFromParcel(parcel);
mGoodsTime = ParcelUtils.readFromParcel(parcel);
}
}
自定义消息布局item_goods_info.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape_bg_white">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/item_goods_img_cover"
android:layout_width="86dp"
android:layout_height="86dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="14dp"
android:layout_marginLeft="12dp"
app:riv_corner_radius="5dp"
android:src="@drawable/ic_goods_cover"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/item_goods_text_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/item_goods_img_cover"
android:layout_marginTop="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:text="北欧风文艺简约棋盘格全棉床单被套四件套纯棉网红三件套床上用品"
android:maxLines="2"
android:maxEms="21"
android:textColor="#000000"
android:textSize="14sp" />
<TextView
android:id="@+id/item_goods_text_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/item_goods_text_name"
android:layout_toRightOf="@+id/item_goods_img_cover"
android:layout_marginTop="12dp"
android:layout_marginLeft="12dp"
android:text="¥148.00"
android:textSize="12sp"
android:textColor="#FF5700" />
<TextView
android:id="@+id/item_goods_text_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/item_goods_text_price"
android:layout_toRightOf="@+id/item_goods_img_cover"
android:layout_marginTop="5dp"
android:layout_marginLeft="12dp"
android:text="江苏南京"
android:textSize="10sp"
android:textColor="#8E8E8E" />
<TextView
android:id="@+id/item_goods_text_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/item_goods_text_price"
android:layout_marginTop="5dp"
android:layout_marginRight="12dp"
android:text="2017-07-16"
android:textSize="10sp"
android:textColor="#8E8E8E"/>
</RelativeLayout>
</LinearLayout>
里面放置了一个ImageView显示商品图片,和4个TextView分别显示商品名称、价格、地区、发布时间。
GoodsInfoRongProvider用来加载定义的消息模板
package com.ranlegeran.rongimtest.message;
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.bumptech.glide.Glide;
import com.makeramen.roundedimageview.RoundedImageView;
import com.ranlegeran.rongimtest.R;
import java.util.List;
import io.rong.imkit.conversation.messgelist.provider.BaseMessageItemProvider;
import io.rong.imkit.model.UiMessage;
import io.rong.imkit.widget.adapter.IViewProviderListener;
import io.rong.imkit.widget.adapter.ViewHolder;
import io.rong.imlib.model.MessageContent;
/**
* 作者 :然了个然
* 简介 :一言以蔽之
* 时间 :2022/4/20 14:21
* 描述 :定义发送商品消息的布局
* 版本 :1.0
*/
public class GoodsInfoRongProvider extends BaseMessageItemProvider<GoodsInfoRongMessage> {
public GoodsInfoRongProvider() {
mConfig.showReadState = true; //修改模板属性
}
@Override
protected ViewHolder onCreateMessageContentViewHolder(ViewGroup parent, int viewType) {
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_goods_info, parent, false);
return new ViewHolder(parent.getContext(), mView);
}
@Override
protected void bindMessageContentViewHolder(ViewHolder holder, ViewHolder parentHolder, GoodsInfoRongMessage goodsInfoRongMessage, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
RoundedImageView mImgGoodsCover = holder.getView(R.id.item_goods_img_cover);
TextView mTextGoodsName = holder.getView(R.id.item_goods_text_name);
TextView mTextGoodsPrice = holder.getView(R.id.item_goods_text_price);
TextView mTextGoodsAddress = holder.getView(R.id.item_goods_text_address);
TextView mTextGoodsTime = holder.getView(R.id.item_goods_text_time);
Glide.with(parentHolder.getContext()).load(goodsInfoRongMessage.getGoodsCover()).into(mImgGoodsCover);
mTextGoodsName.setText(goodsInfoRongMessage.getGoodsName());
mTextGoodsPrice.setText(goodsInfoRongMessage.getGoodsPrice());
mTextGoodsAddress.setText(goodsInfoRongMessage.getGoodsAddress());
mTextGoodsTime.setText(goodsInfoRongMessage.getGoodsTime());
}
@Override
protected boolean onItemClick(ViewHolder holder, GoodsInfoRongMessage goodsInfoRongMessage, UiMessage uiMessage, int position, List<UiMessage> list, IViewProviderListener<UiMessage> listener) {
return false;
}
/**
* 根据消息内容,判断是否为本模板需要展示的消息类型
* @param messageContent 消息内容
* @return 本模板是否处理。
*/
@Override
protected boolean isMessageViewType(MessageContent messageContent) {
return messageContent instanceof GoodsInfoRongMessage && !messageContent.isDestruct();
}
/**
* 在会话列表页某条会话最后一条消息为该类型消息时,会话里需要展示的内容。
* 比如: 图片消息在会话里需要展示为"图片",那返回对应的字符串资源即可。
* @param context context 上下文
* @param goodsInfoRongMessage 会话里需要展示的字符串资源
* @return
*/
@Override
public Spannable getSummarySpannable(Context context, GoodsInfoRongMessage goodsInfoRongMessage) {
return new SpannableString("商品信息");
}
@Override
public boolean showBubble() {
return false;
}
}
最后就是自定义聊天会话界面
activity_my_conversation布局如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MyConversationActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white">
<LinearLayout
android:id="@+id/ll_left_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="15dp">
<ImageView
android:layout_width="17dp"
android:layout_height="17dp"
android:src="@drawable/ic_icon_left_back"/>
</LinearLayout>
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textSize="15sp"
android:textColor="#000000"/>
</RelativeLayout>
<TextView
android:id="@+id/text_send_message"
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:text="点击发送自定义商品消息"
android:textSize="14sp"
android:textColor="#666666"/>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
两个TextView分别用来显示标题和发送自定义消息,FrameLayout用来显示聊天内容。
MyConversationActivity代码如下
package com.ranlegeran.rongimtest;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.ranlegeran.rongimtest.message.GoodsInfoRongMessage;
import com.ranlegeran.rongimtest.message.GoodsInfoRongProvider;
import java.util.ArrayList;
import io.rong.imkit.RongIM;
import io.rong.imkit.config.RongConfigCenter;
import io.rong.imkit.conversation.ConversationFragment;
import io.rong.imkit.utils.RouteUtils;
import io.rong.imlib.IRongCallback;
import io.rong.imlib.RongIMClient;
import io.rong.imlib.model.Conversation;
import io.rong.imlib.model.Message;
import io.rong.imlib.model.MessageContent;
/**
* 作者 : RAN
* 简介 : 一言以蔽之
* 时间 : 2022/04/20 14:47
* 描述 : 自定义聊天界面
* 版本 : 1.0
*/
public class MyConversationActivity extends FragmentActivity {
private LinearLayout mLayoutBack;
private TextView mTextTitle;
private TextView mTextSendMessage;
private String mTargetId; //聊天对话的ID
private String mGoodsCover; //商品图片
private String mGoodsName; //商品名称
private String mGoodsPrice; //商品价格
private String mGoodsAddress; //商品地址
private String mGoodsTime; //商品发布时间
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_conversation);
initView();
}
private void initView() {
mLayoutBack = this.findViewById(R.id.ll_left_back);
mTextTitle = this.findViewById(R.id.text_title);
mTextSendMessage = this.findViewById(R.id.text_send_message);
mTextTitle.setText("对话中");
// 添加会话界面
ConversationFragment mConversationFragment = new ConversationFragment();
FragmentManager mManager = getSupportFragmentManager();
FragmentTransaction mTransaction = mManager.beginTransaction();
mTransaction.replace(R.id.container, mConversationFragment);
mTransaction.commit();
//接收传递过来的数据
Bundle mBundle = getIntent().getExtras();
mTargetId = mBundle.getString(Constants.KEY_TARGET_ID);
mGoodsCover = mBundle.getString(Constants.KEY_GOODS_COVER);
mGoodsName = mBundle.getString(Constants.KEY_GOODS_NAME);
mGoodsPrice = mBundle.getString(Constants.KEY_GOODS_PRICE);
mGoodsAddress = mBundle.getString(Constants.KEY_GOODS_ADDRESS);
mGoodsTime = mBundle.getString(Constants.KEY_GOODS_TIME);
//注册发送商品信息模板
RongConfigCenter.conversationConfig().addMessageProvider(new GoodsInfoRongProvider());
//注册自定义消息
ArrayList<Class<? extends MessageContent>> mMessageList = new ArrayList<>();
mMessageList.add(GoodsInfoRongMessage.class);
RongIMClient.registerMessageType(mMessageList);
//点击返回
mLayoutBack.setOnClickListener(view -> {
finish();
});
//点击发送商品消息
mTextSendMessage.setOnClickListener(view -> {
//如果商品信息为空的话不执行发送逻辑
if (TextUtils.isEmpty(mGoodsCover) && TextUtils.isEmpty(mGoodsName)
&& TextUtils.isEmpty(mGoodsPrice) && TextUtils.isEmpty(mGoodsAddress) && TextUtils.isEmpty(mGoodsTime)) {
Toast.makeText(this, "请检查传递的参数是否正确", Toast.LENGTH_SHORT).show();
} else {
GoodsInfoRongMessage mGoodsInfoRongMessage = new GoodsInfoRongMessage();
mGoodsInfoRongMessage.setGoodsCover(mGoodsCover);
mGoodsInfoRongMessage.setGoodsName(mGoodsName);
mGoodsInfoRongMessage.setGoodsPrice(mGoodsPrice);
mGoodsInfoRongMessage.setGoodsAddress(mGoodsAddress);
mGoodsInfoRongMessage.setGoodsTime(mGoodsTime);
Message mMessage = Message.obtain(mTargetId, Conversation.ConversationType.PRIVATE,mGoodsInfoRongMessage);
RongIM.getInstance().sendMessage(mMessage, null, null, new IRongCallback.ISendMessageCallback() {
@Override
public void onAttached(Message message) {
}
@Override
public void onSuccess(Message message) {
Log.e("MyConversationActivity", "onSuccess: " + message);
}
@Override
public void onError(Message message, RongIMClient.ErrorCode errorCode) {
Log.e("MyConversationActivity", "errorCode: " + errorCode.getValue());
}
});
}
});
}
}
在initView方法中把控件实例化后,然后接收从MainActivity传递过来的参数,同时把刚才定义的消息体和消息模板进行了注册。
在mTextSendMessage.setOnClickListener点击事件中判断了传递过来的参数是否为空,如果为空Toast提示,不为空则发送自定义消息。
以上是接入的全部代码~
|