实现 1 对 1 消息

更新时间: 2023/06/29 05:59:20

本文介绍通过消息聊天组件 chatKit 实现 1 对 1 消息。消息聊天组件 chatKit 底层基于IM UIKit。

功能介绍

1 对 1 消息的主要功能包括会话列表、聊天消息、通知消息和语音输入。

1对1消息.png

前提条件

请确保您已完成以下操作:

准备工作

注册云信 IM 测试账号,获取 accid 和 token

为了方便您调试,您可以在云信控制台注册云信 IM 测试账号,获取 accid 和 token。accid 和 token 将用于登录云信服务端。

  1. 云信控制台的首页单击指定应用名称,进入该应用的详情页面。

  2. 在左侧导航栏选择产品功能 > IM即时通讯,单击基础功能页签。

  3. 测试账号管理区域单击子功能配置
    测试账号管理.png

  4. 在测试账号管理页面,单击新建账号,并填写账号(即accid)、昵称(即 name)、密码(即 Token)后,单击确定

    新建账号.png

配置高德地图参数(地理位置消息功能需要)

地理位置消息功能基于高德地图,因此需要您配置高德地图相关信息。请在您应用的AndroidManifest.xml中配置高德地图的 API Key 和定位服务(APSService)。

xml<!-- 高德地图定位 -->
//在您的applicaion节点中配置高德地图 API Key 和 APSService
<application android:name=".IMApplication">
    <meta-data
        android:name="com.amap.api.v2.apikey"
        android:value="apikey" /> // 传入您获取到的高德地图 API Key
    <service android:name="com.amap.api.location.APSService" />
</application>

开发环境

开发环境要求如下:

环境要求 说明
JDK 版本 JDK 11 及以上版本
Android API 版本 API 21、Android Studio 5.0 及以上版本
Gradle 及所需的依赖库 Gradle Services 页面下载对应版本的 Gradle 及所需的依赖库。
  • Gradle 版本:7.4.1
  • Android Gradle 插件版本: 7.1.3
    关于 Android Gradle 插件、Gradle、SDK Tool 之间的版本依赖关系,请参见 Android Gradle 插件版本说明
CPU架构 ARM 64、ARMV7
IDE Android Studio
其他 依赖 Androidx,不支持 support 库。

示例项目源码

1 对 1 娱乐社交示例项目源码,跑通示例项目的方法请参见跑通示例项目

步骤1:集成 chatKit 组件

  1. 在您的项目的 build.gradle 中,以添加依赖的形式添加相应的 chatKit 组件和第三方库(Glide、Retrofit 和 OkHttp)。

    groovydependencies { 
        // 会话列表功能组件
        implementation("com.netease.yunxin.kit.conversation:conversationkit-ui:${LATEST_VERSION}")
        // 聊天功能组件
        implementation("com.netease.yunxin.kit.chat:chatkit-ui:${LATEST_VERSION}")
        //图片库
        implementation("com.netease.yunxin.kit.common:common-image:1.1.6")  
        //网络库
        implementation("com.squareup.retrofit2:retrofit:2.9.0")
        implementation("com.squareup.retrofit2:converter-gson:2.9.0")
        implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
        implementation("com.squareup.okhttp3:okhttp:4.9.3")
        //地理位置消息
        implementation("com.netease.yunxin.kit.locationkit:locationkit:${LATEST_VERSION}")
    }
    

    如果是 kotlin 脚本配置,在应用根目录下,打开build.gradle.kts,声明云信 IM UIKit 代码仓库。

    javadependencies {
        api("com.netease.yunxin.kit.contact:contactkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.qchat:qchatkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.conversation:conversationkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.team:teamkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.chat:chatkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.search:searchkit-ui:${LATEST_VERSION}")
        api("com.netease.yunxin.kit.locationkit:locationkit:${LATEST_VERSION}")
    }
    
    

  2. 在项目根目录下,打开gradle.propertes, 修改资源依赖配置。

    javaandroid.nonTransitiveRClass=false
    

    如果导入之后,出现资源找不到的问题,请修改此处配置。
    IM UIKit 模块化设计,所以存在资源依赖,以减少包体积。

  3. 配置防止代码混淆

    代码混淆是指使用简短无意义的名称重命名已存在的类、方法、属性等,增加逆向工程的难度,保障 Android 程序源码的安全性。

    为了避免因上述的重命名而导致调用 chatKit 异常,请在 proguard-rules.pro 文件中加入以下代码,将 NIM SDK 和 IM UIKit 的相关类加入不混淆名单。

    java## NIM SDK 防混淆
    -dontwarn com.netease.nim.**
    -keep class com.netease.nim.** {*;}
    
    -dontwarn com.netease.nimlib.**
    -keep class com.netease.nimlib.** {*;}
    
    -dontwarn com.netease.share.**
    -keep class com.netease.share.** {*;}
    
    -dontwarn com.netease.mobsec.**
    -keep class com.netease.mobsec.** {*;}
    
    ## 如果你使用全文检索插件,需要加入
    -dontwarn org.apache.lucene.**
    -keep class org.apache.lucene.** {*;}
    
    ## IM UIKit 防混淆
    -dontwarn com.netease.yunxin.kit.**
    -keep class com.netease.yunxin.kit.** {*;}
    -keep public class * extends com.netease.yunxin.kit.corekit.XKitInitOptions
    -keep class * implements com.netease.yunxin.kit.corekit.XKitService {*;}
    
    ## 呼叫组件防混淆
    -keep class com.netease.lava.** {*;}
    -keep class com.netease.yunxin.** {*;}
    
    -dontwarn com.netease.yunxin.kit.**
    -keep class com.netease.yunxin.kit.** {*;}
    -keep public class * extends com.netease.yunxin.kit.corekit.XKitInitOptions
    -keep class * implements com.netease.yunxin.kit.corekit.XKitService {*;}
    
    ## glide 4
    -keep public class * implements com.bumptech.glide.module.GlideModule
    -keep public class * extends com.bumptech.glide.module.AppGlideModule
    -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
        **[] $VALUES;
        public *;
    }
    
    ## okhttp
    -dontwarn okhttp3.**
    -keep class okhttp3.**{*;}
    
    ## 如果你使用全文检索插件,需要加入
    -dontwarn org.apache.lucene.**
    -keep class org.apache.lucene.** {*;}
    
    ## 如果你开启数据库功能,需要加入
    -keep class net.sqlcipher.** {*;}
    
    

步骤2:初始化 chatKit

SDKOptions options = NimSDKOptionConfig.getSDKOptions(this, “your app key”);
LoginInfo info = new LoginInfo("account","token");      // account 和 token 请替换为 IM 的 accid 和 Token
IMKitClient.init(this, info, options);
if (IMKitUtils.isMainProcess(this)) {
   ChatKitClient.init(this);
   //初始化位置消息模块
   LocationKitClient.init();
}

步骤3:登录IM

如果登录信息(LoginInfo)在初始化的时候已传入,则不需要再进行本节内容介绍的登录步骤。


如果不能在 Applicatiton 中获取登录信息LoginInfo,需调用IMKitClient类中的loginIM方法登录。

调用登录的方法时,将如下示例代码中的 accidtoken 分别替换为您的云信账号 ID (即 accid)和 Token。

javaLoginInfo loginInfo = LoginInfo.LoginInfoBuilder.loginInfoDefault("accid","token").build();
IMKitClient.loginIM(loginInfo,new LoginCallback<LoginInfo>() {
    @Override
    public void onError(int errorCode, @NonNull String errorMsg) {
        //登录失败
    }

    @Override
    public void onSuccess(@Nullable LoginInfo data) {
        //登录成功
    }
});

步骤4:界面搭建

1 对 1 娱乐社交消息系统常用的功能包括会话列表和聊天界面,本文介绍 chatKit 如何基于 IM UIKit 搭建相应界面。

搭建会话列表

基于 Fragment 方式集成 IM UIKit 的会话列表,具体步骤请参见IM UIKit 的集成会话列表

搭建聊天界面

通过会话消息模块(chatkit-ui)搭建聊天界面,实现接收和发送基本的消息类型,包括文本消息、图片消息、语音消息、视频消息、表情和地理位置消息。

IM UIKit 提供的单聊 Fragment 的类名为ChatP2PFragment。您可以在应用的 Activity 中集成ChatP2PFragment从而构建您的单聊界面。

本节假设您的应用的 Acitiviy 为MyChatP2PActivity,进行相应说明。

  1. 创建您的应用 Activity 的布局文件my_chat_p2p_activity.xml

    java<?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:ignore="MissingDefaultResource">
    
    //用来放置Fragment
        <FrameLayout
            android:id="@+id/chat_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
    
  2. 创建 Activity。本步骤以应用的 Acitivity 采用视图绑定(ViewBinding)模式为例进行说明。

    javapublic class MyChatP2PActivity extends AppCompatActivity {
    
    
        private MyChatP2PActivityBinding viewBinding;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            viewBinding = MyChatP2PActivityBinding.inflate(LayoutInflater.from(this));
            //设置Activity界面布局,非ViewBinding采用setContentView(R.layout.my_chat_p2p_activity);
            setContentView(viewBinding.getRoot());
            UserInfo userInfo = (UserInfo) getIntent().getSerializableExtra(RouterConstant.CHAT_KRY);
            if (userInfo == null) {
                finish();
            }
            FragmentManager fragmentManager = getSupportFragmentManager();
            //创建ChatP2PFragment
            P2PChatFragmentBuilder fragmentBuilder = new P2PChatFragmentBuilder();
            ChatP2PFragment chatFragment = fragmentBuilder.build();
            Bundle bundle = new Bundle();
            bundle.putSerializable(RouterConstant.CHAT_KRY, userInfo);
            chatFragment.setArguments(bundle);
            //将ChatP2PFragment加载到Activity中
            fragmentManager
                    .beginTransaction()
                    .add(R.id.chat_container, chatFragment)
                    .commitAllowingStateLoss();
    
        }
    }
    
    
  3. AndroidManfest.xml中声明 Activity。

    java<activity
            android:name=".activity.MyChatP2PActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateHidden|adjustResize">
    </activity>
    
  4. 注册会话界面。

    调用XKitRouter中的会话注册方法registerRouter,同时进行相应配置(参考本步骤的示例代码),将 IM UIKit 的默认会话界面替换为您的 Activity 界面。

    • 方法原型
    javaXKitRouter.registerRouter(path,activity);
    
    • 参数说明
    参数 类型 说明
    path String Activity 的跳转地址
    activity Class 跳转的目标 Activity
    • 示例代码
    javaXKitRouter.registerRouter(RouterConstant.PATH_CHAT_P2P_PAGE, MyChatP2PActivity.class);
    
  5. 使用 IM UIKit 提供的路由器XKitRouter进行跳转。

    javaXKitRouter.withKey(RouterConstant.PATH_CHAT_P2P_PAGE).withContext(context).navigate();
    

实现通知消息

通知消息.png

调用以下方法实现发送通知消息,通知消息只有本端可见,对方不可见。

示例代码如下:

    //  注册自定义消息解析
    ChatKitClient.addCustomAttach(
        OneOnOneChatCustomMessageType.ACCOST_MESSAGE_TYPE, AccostMessageAttachment.class);
    ChatKitClient.addCustomAttach(
        OneOnOneChatCustomMessageType.TRY_AUDIO_CALL_MESSAGE_TYPE,
        TryAudioCallMessageAttachment.class);
    ChatKitClient.addCustomAttach(
        OneOnOneChatCustomMessageType.TRY_VIDEO_CALL_MESSAGE_TYPE,
        TryVideoCallMessageAttachment.class);
   //   注册自定义消息UI 
            ChatKitClient.addCustomViewHolder(
        OneOnOneChatCustomMessageType.ACCOST_MESSAGE_TYPE, AccostMessageViewHolder.class);
    ChatKitClient.addCustomViewHolder(
        OneOnOneChatCustomMessageType.TRY_AUDIO_CALL_MESSAGE_TYPE,
        TryAudioCallMessageViewHolder.class);
    ChatKitClient.addCustomViewHolder(
        OneOnOneChatCustomMessageType.TRY_VIDEO_CALL_MESSAGE_TYPE,
        TryVideoCallMessageViewHolder.class);

搭建语音输入页面

按住说话1.png

示例代码如下:

// 显示语音录制弹窗
  private void showAudioInputDialog() {
    if (!PermissionUtils.hasPermissions(this, Manifest.permission.RECORD_AUDIO)) {
      permissionLauncher.launch(new String[] {Manifest.permission.RECORD_AUDIO});
      return;
    }
    audioInputManager.initAudioRecord(this);
    audioInputManager.startAudioRecord();
    if (dialog == null) {
      dialog = new AudioInputDialog(CustomChatP2PActivity.this, sessionId);
    }
    if (!dialog.isShowing()) {
      dialog.show();
      dialog.showCancelAudioSendUI(false);
    }
  }

  // 结束语音录制并隐藏语音录制弹窗
      audioInputManager.endAudioRecord(isInsideView);
      dismissAudioInputDialog();

 // 暂停语音录制
   AudioInputManager.getInstance().pause();

  // 销毁语音录制
   AudioInputManager.getInstance().destroy();    
   // 监听语音输入按钮触摸事件
    mAudioTv.setOnTouchListener(
        (v, event) -> {
          if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (OneOnOneUtils.isInVoiceRoom()) {
              ToastX.showShortToast(R.string.one_on_one_other_you_are_in_the_chatroom);
            } else {
              showAudioInputDialog();
            }
          } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (dialog != null) {
              int x = (int) event.getRawX();
              int y = (int) event.getRawY();
              isInsideView = dialog.getFlViewRect().contains(x, y);
              dialog.showCancelAudioSendUI(isInsideView);
            }
          } else if (event.getAction() == MotionEvent.ACTION_UP) {
            if (dialog != null) {
              audioInputManager.endAudioRecord(isInsideView);
              dismissAudioInputDialog();
            }
          }
          return true;
        });


    // 语音输入弹窗UI
    AudioInputDialog
    // 语音输入逻辑     
    AudioInputManager
    // 初始化语音录制
  public void initAudioRecord(Context context) {
    ALog.i(TAG, "initAudioRecord,context:" + context);
    this.context = context;
    if (mAudioRecorder == null) {
      mAudioRecorder = new AudioRecorder(context, RecordType.AAC, MAX_DURATION, this);
    }
  }

    // 开始语音录制
     public void startAudioRecord() {
    ALog.i(TAG, "startAudioRecord");
    if (context instanceof Activity) {
      ((Activity) context)
          .getWindow()
          .setFlags(
              WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
              WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
    if (mAudioRecorder != null) {
      mAudioRecorder.startRecord();
    }
    timer = new Timer();
    TimerTask timerTask =
        new TimerTask() {
          @Override
          public void run() {
            if (!started) {
              return;
            }
            currentMilliSecond = currentMilliSecond + PERIOD;
            if (audioInputCallback != null) {
              audioInputCallback.onAudioRecordProgress(
                  (int) (currentMilliSecond * 1.0 / MAX_SECOND_MILLIS * 100));
            }
            if (currentMilliSecond >= MAX_SECOND_MILLIS) {
              endAudioRecord(false);
            }
          }
        };
    timer.schedule(timerTask, 0, PERIOD);
  }

// 结束语音录制
  public void endAudioRecord(boolean cancel) {
    ALog.d(TAG, "endAudioRecord -->> cancel:" + cancel);
    reset();
    mainHandler.post(
        () -> {
          if (context instanceof Activity) {
            ((Activity) context)
                .getWindow()
                .setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
          }
          if (mAudioRecorder != null) {
            mAudioRecorder.completeRecord(cancel);
          }
        });
  }
此文档是否对你有帮助?
有帮助
去反馈
  • 功能介绍
  • 前提条件
  • 准备工作
  • 开发环境
  • 示例项目源码
  • 步骤1:集成 chatKit 组件
  • 步骤2:初始化 chatKit
  • 步骤3:登录IM
  • 步骤4:界面搭建
  • 搭建会话列表
  • 搭建聊天界面
  • 实现通知消息
  • 搭建语音输入页面