Android

Android

【社区精华|持续更新】收录本社区精华内容,手把手教学IM/RTC开发!

IM即时通讯admin 发表了文章 • 8 个评论 • 778 次浏览 • 2020-12-07 14:41 • 来自相关话题

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。Android篇融云即时通讯SDK集成 — 通知检查融云 IM SDK 集成 —- 刷新会话界面... ...查看全部

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。

微信截图_20201207144054.png

Android篇

融云即时通讯SDK集成 — 通知检查

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

Android 端如何添加自定义表情

解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

融云清空历史消息 Android 端

唠一唠融云的消息扩展功能

融云 IMkit 拦截或监听所有发送消息

融云如何把图片消息的图片上传到自己的文件服务器

唠一唠融云 VIVO push 无法跳转的解决方案

融云即时通讯SDK集成 — 定制UI(一) ——会话界面小改动

融云即时通讯SDK集成 — 定制UI(二) ——添加自定义表情库

融云即时通讯SDK集成 — 定制UI(三) ——兼容Android Q

融云如何把图片消息的图片上传到自己的文件服务器

融云即时通讯SDK集成 — 华为推送的点击跳转处理

带你实现女朋友欲罢不能的 App

Flutter 集成融云 sdk

配置融云SDK的自签证书

自定义消息 包含 list 数组

关于融云聊天室KV 值的正确使用

融云 IM SDK 转 AndroidX

融云即时通讯SDK集成 — 国内厂商推送集成踩坑篇(Android平台)

在融云 IMkit 会话界面基础上添加消息已读未读

融云聊天室属性 kv

融云 ConversationListFragment 会话列表添加头部布局

融云即时通讯SDK集成 — FCM推送集成指南(Android平台)

融云集成之避坑指南-Android推送篇

融云IMKit 动态删除或添加plugin 的实现


iOS篇

iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4

如何隐藏融云输入框语音按钮

给融云的输入框上方加个功能按钮,怎么整?

融云 IM SDK 如何插入消息

集成融云 IMLib 时,如何实现一套类似于 IMKit 的用户信息管理机制

为融云聊天页面的输入框添加 Placeholder

30 分钟集成融云 IM 即时通讯

简单介绍融云 imkit 包含功能

融云的聊天页面在 iOS14 出现崩溃的解决办法

融云聊天页面长按消息后“翻译”功能的实现方法

使用融云 IM 点击最近聊天记录时跳转到 @ 自己的消息

如何设置融云用户信息

自定义融云会话列表 cell 选中背景

融云 IMKit 音频录制参数

融云会话页面刷新不及时问题

融云 Flutter IM SDK 解析

关于融云 SDK 在使用 p8 证书的坎坷~

融云 SDK 如何实现群组操作

如何利用融云 IMLib 来实现一个阅后即焚功能

干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执


Web篇

作为小白接融云 IM SDK 新路体验~

微信小程序集成融云 SDK (即时通讯) 集成必备条件

Web 端使用融云 SDK 集成实现滑动加载历史消息

融云IM SDK web 端集成 — 表情采坑篇

融云 Web SDK 如何实现表情的收发 ?

集成融云小程序 SDK 遇到的问题

使用融云 Web SDK 撤回消息

融云 RTC SDK 集成实现直播,趟坑之旅~~~

融云 Web SDK 删除历史消息

集成融云小程序 SDK 遇到的问题

Web 端集成融云 SDK 如何发送正确图片消息给移动端展示?

使用融云 IM SDK 实现 H5 直播聊天

WebRTC 实现实时音视频技术研究

融云发送语音消息

融云 CallLib 集成遇到的问题

结合融云 WebSDK 了解 WebSocket 基本原理

集成融云 Web 音视频通话踩坑之旅

SDK 兼容 JSON

融云 IM SDK 发送语音消息

集成融云 IM 问题总结

融云 Web SDK 如何实现只有一个设备登入

融云 Web 播放声音 — Flash 篇 (播放 AMR、WAV)

融云 IM 那些事儿

融云 AMR(Aduio) 播放 AMR 格式 Base64 码音频


社区福利

【领取见面礼】限量 100份 GeekOnline加油包!等你来拿

【有奖调研】Geek Online 2020 编程挑战赛参赛调研

【征稿活动】Geek Online 社区第一期投稿激励计划已启动!


GeekOnline编程挑战赛

Geek Online 2020 编程挑战赛官网

重磅!Geek Online 2020 编程挑战赛来了!

Geek Online 2020 编程挑战赛 GitHub 仓库

2 个月激烈角逐,15 支队伍突围决赛路演!Geek Online 2020 编程挑战赛完美收官!

一张图回顾 Geek Online 2020 编程挑战赛精彩瞬间!

“这些项目不是什么赚大钱的项目,但是它们足够有趣。”丨关于 Geek Online 2020 编程挑战赛,选手们如是说

融云 CTO 杨攀: Geek Online 2020 编程挑战赛 让开发者站上 C 位

【参赛攻略】你想了解的Geek Online 2020 编程挑战赛常见问题这里都有!

【融云集成常见问题整理】Geek Online 2020 编程挑战赛选手提问整理


求职招聘

【招聘】寻一枚熟悉融云IM的开发工程师,坐标合肥,待遇从优

和50万优质程序员一起成长——程序员客栈招聘

持续更新....

【Android开发】如何使用融云的消息扩展

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 116 次浏览 • 2021-02-24 19:30 • 来自相关话题

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html总结文... ...查看全部

微信截图_20210224192550.png

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html

总结文档中的几个重点:

  • 4.0.3之前的版本无法使用。

  • 消息扩展是 Message类的属性,也就是说自定义消息也可以使用。

  • 单条消息只能设置300个kv,(某些场景不可用,比如在群里发400个人可领的红包)。

  • 仅支持单聊、群聊。

  • key和value都是字符串类型,并且key不能是中文,否则会报错INVALID_PARAMETER。

  • 用IMKit的UI发送消息时需要拦截一下,设置消息扩展开关。

使用步骤:

  1. 消息的发送端必须给需要消息扩展的消息开开关,没有全局设置,只能单条消息设置。分以下两种情况:

  2. 如果使用的是 IMKit 提供的UI发送消息,在 ConversationFragment中的 onSendToggleClick 方法发送消息,在 sendMessage 前调用下面的方法

    /**
     * 设置消息扩展信息列表
     * 

扩展信息只支持单聊和群组,其它会话类型不能设置扩展信息。


     *
     * @param expansion 消息扩展信息列表
     */
    public void setExpansion(HashMap<String, String> expansion) {
        this.expansion = expansion;
    }

还需要设置 canIncludeExpansion 和 expansionDic 。

  • 如果是自己调用 RongIM 和 RongIMClient 接口发的消息,用 MessageContent 类的对象构造一个 Message 对象,然后设置 canIncludeExpansion 和 expansionDic 再调用RCIM中对应的 send 方法将消息发送出去即可。

  • 更新和删除扩展的操作可以参考官方文档。

  • 想要实时捕获消息扩展的更改和删除,就需要设置回调,在回调中更新UI。设置回调可参考官方文档。

融云官网:https://www.rongcloud.cn/

文档频道:https://docs.rongcloud.cn/v4


一把双刃剑 -- 融云即时通讯sdk中的自定义消息使用心得&指南 (下)

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 104 次浏览 • 2021-02-24 19:30 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20210224192919.png

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).

他家的官网和文档地址: 官网:https://www.rongcloud.cn/ 文档:https://docs.rongcloud.cn/v4

这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面).

心得 (下)

自定义小视频消息

接上篇对自定义消息的开发心得哈.

因为融云家自带的小视频消息是需要收费的, 需要在服务端开通小视频服务后, 同时在端上做一下配置, 才可以使用小视频消息. 我一看这还得了, 想方设法收我钱呢不是. 不过他家只是对小视频类型的消息在服务端做了限制, 而不是完全不让在消息中携带视频链接. 自定义消息是随便自定义的, 那么我自定义一个小视频消息不就好啦.

大概实现思路如下:

自定义小视频消息继承MediaMessageContent,其中mLocalPath是小视频文件本地的存放路径,mMediaUrl是小视频文件上传到文件服务器后的http/https地址。

小视频的拍摄,播放我们RongCloud SDK没有接口,开发者自己实现。

当拍摄完成,发送小视频消息时使用方法

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallback callback)或者

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallbackWithUploader callback)

这两个方法的不同是后者开发者负责小视频文件的上传到指定的服务器,前者使用我们RongCloud默认的文件服务器

以上是大致步骤,小视频开发过程中可能遇到的问题,说明如下:

1.关于缩略图的处理,我们SDK没有直接上传一张图片返回一个url地址的接口,开发者可以把缩略图上传到自己的服务器,这样缩略图跟mMediaUrl类似,小视频消息展示显示缩略图时加载一张网络图片即可。

另一种缩略图处理方式类似我们SDK发送图片消息时的缩略图处理,把缩略图做base64编码,放到自定义消息体中直接传输,这种方式涉及到消息发送时把缩略图转化为base64数据和接收到消息时还原为缩略图,在我们SDK内部使用的是MessageHandler。

关于MessageHandler,我们RongCloud的每个消息都有一个MessageHandler,此前我们文档从没有介绍过这个

MessageHandler,对用户透明的,用户的自定义消息没有指定它是因为有个默认的DefaultMessageHandler。

自定义消息时可以指定自己的MessageHandler,例如图片消息的定义如下

ImageMessage.png

MessageHandler在消息发送和接收时在IPC进程中会被自动调用,它有两个方法,encodeMessage

和decodeMessage,在消息接收后调用decodeMessage时开发者可以把base64对应的数据转化为缩略图url,这样在展示缩略图时直接使用url即可。

/**
 * 解码 {@link MessageContent} 到 {@link Message} 中。
 *
 * @param message 用于存放 MessageContent 的消息实体。
 * @param content 将要被解码的 MessageContent。
 */
public abstract void decodeMessage(Message message, T content);

/**
 * 对 {@link Message} 编码。
 *
 * @param message 将要被编码的 Message 实体。
 */
public abstract void encodeMessage(Message message);

此文档包含了两个附件分别为自定义小视频消息和对应的小视频消息MessageHandler,供开发者参考

2.开发中可能还会遇到小视频文件上传时进度更新的问题,如果开发者自定义的小视频消息不继承自MediaMessageContent而是MessageContent,需要自己在UI上维护上传进度


融云 Android 端如何清空某一个会话的聊天记录

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 107 次浏览 • 2021-02-24 19:30 • 来自相关话题

融云 Android 端如何清空某一个会话的聊天记录实现须知没有开通 “历史消息云存储”,只能删除本地的数据。在融云控制台开通 “历史消息云存储功能” 后,才可以删除远端消息。实现步骤1.调用 cleanHistoryMessages 方法,cleanRemo... ...查看全部

融云 Android 端如何清空某一个会话的聊天记录

微信截图_20210224192137.png

实现须知

没有开通 “历史消息云存储”,只能删除本地的数据。在融云控制台开通 “历史消息云存储功能” 后,才可以删除远端消息。

实现步骤

1.调用 cleanHistoryMessages 方法,cleanRemote 传 true ,recordTime 传 当前的时间戳。

/**
     * 删除指定时间戳之前的消息,可选择是否同时删除服务器端消息
     * 

此方法从服务器端清除历史消息,但是必须先开通历史消息云存储功能。


     * 

根据会话类型和 TargetId 清除某一会话指定时间戳之前的本地数据库消息(服务端历史消息),
     * 清除成功后只能从本地数据库(服务端)获取到该时间戳之后的历史消息。


     *
     * @param conversationType 会话类型。
     * @param targetId         会话目标ID。
     * @param recordTime       清除消息截止时间戳,【0 ~ 当前时间的 Unix 时间戳】。
     * @param cleanRemote      是否删除服务器端消息
     * @param callback         清除消息的回调。
     */
    public void cleanHistoryMessages(final Conversation.ConversationType conversationType,
                                     final String targetId,
                                     final long recordTime,
                                     final boolean cleanRemote,
                                     final OperationCallback callback)

2.您使用的是 imlib ,还需要自行刷新您的界面。 3.您使用的是 imkit 调用 ConversationFragment 的 getMessageAdapter 方法,获取到会话界面消息展示适配器,移除数据并且刷新数据源。

mListAdapter.removeAll();
mListAdapter.notifyDataSetChanged();

4.卸载重装或者更换设备登陆后,历史消息还可以拉取到,来保证用户更换设备或者卸载重装后还可以有上下文。如果您不希望换端后再收到这些消息,您可以 提供一下 appkey 提交工单,我们帮您把这部分数据的缓存功能关闭。

Flutter Platform Channel深度解析

IM即时通讯admin 发表了文章 • 0 个评论 • 149 次浏览 • 2021-01-28 14:20 • 来自相关话题

一、简介Platform Channel 是 Flutter 端与 Platform 端制定的通信机制,由官方提供用于 Dart 和平台之间的相互通信。分为以下 3 种(1)BaseMessageChannel :用于传递字符串和半结构化的信息(在大内存数据块... ...查看全部

一、简介

Platform Channel 是 Flutter 端与 Platform 端制定的通信机制,由官方提供用于 Dart 和平台之间的相互通信。

分为下 3 种

(1)BaseMessageChannel :用于传递字符串和半结构化的信息(在大内存数据块传递的情况下使用)

(2)MethodChannel:用于传递方法调用(Method Invocation

(3)EventChannel: 用于数据流(Event Streams)的通信

 

二、消息传递与编码器

Flutter 的消息传递工具是 BinaryMessager ,通过它与 Platform 建立起通信关系,消息以二进制的格式进行传递。

1.png

如图所示 BinaryMessager 的传递需要经过 BinaryMessageHandler,BinaryMessagerHandler 是以 Channel Name 作为键值生成出来再被注册到 BinaryMessager 上的,BinaryMessageHandler 和 BinaryMessager 是一一对应的,二进制格式的消息通过消息编码器(Codec)解码为识别的信息,并传递给 Handler 进行处理。Handler 处理完后,会把结果编码为二进制格式,再通过回调函数返回结果并发送回 Flutter 端

1.编码器分类

(1)MessageCodec:BinaryCodec、StringCodec、JSONMessageCodec、StandardMessageCodec

(2)MethodCodec:JSONMethodCodec、StandardMessageCodec

2.png

经过消息编码器处理后,消息就可以被 Handler 进行处理了

 

2.消息编码过程

Android 端的返回值是 java.lang.Integer 类型的,而 iOS 端返回值则是一个 NSNumber 类型的(通过NSNumber numberWithInt:获取)。而到了 Flutter 端时,这个返回值自动变成Dart 语言的 Int 类型。

standard platform channels 使用 standard messsage codec message response 进行序列化和反序列化,message response 可以是 booleans, numbers, Strings, byte buffers,List, Maps 等等,而序列化后得到的则是二进制格式的数据

Flutter 默认的消息编码器是 StandardMessageCodec ,支持的数据类型如下:3.png

三、MethodChannel

MethodChannel 是 Flutter 与 Platform之间传递信息的一种,其传递过程是:BinaryMessager > BinaryMessagerHandler > MethodChannel。

4.png

如上图:Native 端(iOS 和 Android)为宿主端(host)Flutter 则是客户端(client),Flutter 调用 Native 方法时,需要传递的信息是通过平台通道传递到宿主端的,Native 收到调用的信息后方可执行指定的操作。如有返回的数据,则 Native 会将数据再通过平台通道一并传递给 Flutter,其中数据传递是异步的,这样就能确保消息传递时用户界面不会被阻塞。

 

1.Flutter 层(Dart 层)

Flutter 端使用 MethodChannel 的 invokeMethod 方法发起一次方法调用时,开始了消息传递流程。 invokeMethod 方法会将其入参 message 和 arguments 封装成一个 MethodCall 对象,并使用 MethodCodec 将其编码为二进制格式数据,再通过 BinaryMessages 将消息发出。(注意,此处提到的类名与方法名均为 Dart 层的实现)

 上述过程最终会调用到 ui.Window 的 _sendPlatformMessage 方法,该方法是一个 Native 方法,其实现在 Native 层,这与 Java 的 JNI 技术非常类似。我们向 Native 层发送了三个参数:

            name,String 类型,代表 Channel 名称

            dataByteData 类型,即之前封装的二进制数据

            callback,Function 类型,用于结果回调

2.Native

Native 层后,window.cc 的 SendPlatformMessage 方法接受了来自 Dart 层的三个参数,并对它们做了一定的处理:Dart 层的回调 callback 封装为 Native 层的 PlatformMessageResponseDart 类型的 response;dart 层的二进制数据 data 转化为 std::vector<uint8t> 类型数据 data;根据 response, data 以及 Channel 名称 name 创建一个 PlatformMessage 对象,并通过 dartstate->window()->client()->HandlePlatformMessage 方法处理 PlatformMessage 对象。

dart_state->window()->client() 是一个 WindowClient,而其具体的实现为 RuntimeController,RuntimeController 会将消息交给其代理 RuntimeDelegate 处理。

RuntimeDelegate 的实现为 Engine,Engine 在处理 Message 时,会判断该消息是否是为了获取资源(channel 等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用 Engine::Delegate 的 OnEngineHandlePlatformMessage 方法。

Engine::Delegate 的具体实现为 Shell,其 OnEngineHandlePlatformMessage 接收到消息后,会向 PlatformTaskRunner 添加一个 Task,该 Task 会调用 PlatformView 的 HandlePlatformMessage 方法。值得注意的是,Task 中的代码执行在 Platform Task Runner 中,而之前的代码均执行在 UI Task Runner 中。

四、消息处理

PlatformView 的 HandlePlatformMessage 方法在不同平台有不同的实现,但是其基本原理是相同的

5.png

1.PlatformView

AndroidPlatformViewAndroid 是 Platformview 的子类,也是其在 Android 端的具体实现。当 PlatformViewAndroid 接收到 PlatformMessage 类型的消息时,如果消息中有 response(类型为 PlatformMessageResponseDart),则生成一个自增长的 responseid,并以 responseid 为 key,response 为 value 存入字典 pendingresponses 中。接着,将 channel 和 data 均转化为 Java 可识别的数据,通过 JNI Java 层发起调用,将 response_id、channel 和 data 传递过去。

 Java 层中,被调用的代码为 FlutterNativeView (BinaryMessager 的具体实现)的 handlePlatformMessage ,该方法会根据 channel 找到对应的 BinaryMessageHandler 并将消息传递给它处理。

 BinaryMessageHandler 处理完成后,FlutterNativeView 会通过 JNI 调用 native 的方法,将 responsedata 和 responseid 传递到 native 层。

 Native 层,PlatformViewAndroid 的 InvokePlatformMessageResponseCallback 接收到了respondid 和 responsedata。其先将 responsedata 转化为二进制结果,并根据 responseid,从 pandingresponses 中找到对应的 PlatformMessageResponseDart 对象,调用其 Complete 方法将二进制结果返回。

2.PlatformViewIOS

PlatformViewIOS 是 PlatformView 的子类,也是其在 iOS 端的具体实现,当 PlatformViewIOS 接收到 message 时会交给 PlatformMessageRouter 处理。

PlatformMessageRouter 通过 PlatformMessage 中的 channel 找到对应的 FlutterBinaryMessageHandler,并将二进制消息其处理,消息处理完成后,直接调用 PlatformMessage 对象中的 PlatformMessageResponseDart 对象的 Complete 方法将二进制结果返回

3.结果回传

PlatformMessageResponseDart 的 Complete 方法向 UI Task Runner 添加了一个新的 Task,这个 Task 的作用是将二进制结果从 native 的二进制数据类型转化为 Dart 的二进制数据类型 response,并调用 Dart 的 callback 将 response 传递到 Dart 层。

Dart 层接收到二进制数据后,使用 MethodCodec 将数据解码,并返回给业务层。至此,一次从 Flutter 发起的方法调用就完整结束了

五、具体使用

6.png

1.Flutter 端调用 Android 方法

2.Android 端代码

1)继承 MethodCallHandler 并设置 Handler MethodChannel 需要保存在对象一会调用回调时需要使用,onMethodCall Flutter 层回调的方法这边用 RCIMFlutterWrapper 承接处理

7.png2RCIMFlutterWrapper 类中处理, MethodCall Method,对应 Flutter 层调用 invokeMethod 方法的传入的第一个参数,两端需完全对应一致

8.png

9.png

(3)直接通过 result 对象回调回去这样就能将结果回调

3.关于 Android 回调 Flutter 的使用

10.png

(1)Flutter 端回调监听,设置监听 Key 两端对应

 11.png

(2)Android 端代码回调, mChannel.invokeMethod 方法将数据回调给 Flutter 层



Android 端如何添加自定义表情

IM即时通讯大兴 发表了文章 • 0 个评论 • 567 次浏览 • 2020-12-02 16:28 • 来自相关话题

实现步骤1.新建 RongEmoticonTab 类继承 IEmoticonTab 。public class RongEmoticonTab implements IEmoticonTab {  &... ...查看全部

微信截图_20201202162412.png

实现步骤

1.新建 RongEmoticonTab 类继承 IEmoticonTab 。

public class RongEmoticonTab implements IEmoticonTab {
    public RongEmoticonTab() {
    }
    @Override
    public Drawable obtainTabDrawable(final Context context) {
        return context.getResources().getDrawable(R.drawable.u1f603);
    }
    @Override
    public View obtainTabPager(Context context) {
        return view;
    }
    @Override
    public void onTableSelected(int i) {
    }
}

2.在 obtainTabPager 中添加您想要展示在表情面板上的 view 。

 @Override
    public View obtainTabPager(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.view_emoji, null);
        RecyclerView rv = view.findViewById(R.id.recycler_view);
        //LinearLayoutManager是用来做列表布局,也就是单列的列表
        GridLayoutManager mLayoutManager = new GridLayoutManager(context, 5, OrientationHelper.VERTICAL, false);
        rv.setLayoutManager(mLayoutManager);
        //谷歌提供了一个默认的item删除添加的动画
        rv.setItemAnimator(new DefaultItemAnimator());
        rv.setHasFixedSize(true);
        //模拟列表数据
        ArrayList newsList = new ArrayList<>();
        TypedArray array = context.getResources().obtainTypedArray(context.getResources().getIdentifier("rc_emoji_res", "array", context.getPackageName()));
        int i = -1;
        while (++i < array.length()) {
            newsList.add(array.getResourceId(i, -1));
        }
        rv.setAdapter(new NewsAdapter(newsList));
        return view;
    }

3.Adapter 和布局文件可以参考GitHub
4.自定义一个 ExtensionModule 继承自 DefaultExtensionModule,复写其中的 getEmoticonTabs() 方法,返回需要展示的 EmoticonTab 列表。

public class MyExtensionModule extends DefaultExtensionModule {
    private RongEmoticonTab rongEmoticon;
    @Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        RongEmoticonTab emojiTab=new RongEmoticonTab();
        emoticonTabs.add(myEmoticon);
        return emoticonTabs;
    }
}

5.在初始化之后,取消 SDK 默认的 ExtensionModule,注册自定义的 ExtensionModule, 如下:

public void setMyExtensionModule() {
    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 MyExtensionModule());
        }
    }
}

6.如果需要网络下载表情需要下载并持久化表情数据,需要在添加 EmoticonTab 前下载好表情数据。

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 678 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 658 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

IM即时通讯大兴 发表了文章 • 0 个评论 • 575 次浏览 • 2020-12-02 16:28 • 来自相关话题

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.之前我们是使用的2.x 版本, 使用下面接口配置一下即可.    RongIMClient.getInsta... ...查看全部

微信截图_20201202162800.png

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.

之前我们是使用的2.x 版本, 使用下面接口配置一下即可.

    RongIMClient.getInstance().enableHttpsSelfCertificate(false);

但是最近我们升级融云到 4.0 版本. 突然发现连接不上了. 日志中报

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

这个错误.

于是咨询融云技术支持, 得知 4.0 需要自己进行证书验证.

需在 application 中添加即可.

 try {
    //使用X509TrustManager代替CertificateTrustManager跳过证书验证。
    TrustManager tm[] =  new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };
        SSLContext context = SSLContext.getInstance("TLS");   
        context.init(null, tm, null);
        SSLUtils.setSSLContext(context);
        SSLUtils.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
             }
            });
} catch (Throwable e) {
    throw new IllegalStateException(e);
}

注意的是, 由于导航请求是在融云的 ipc 进程中的, 所以在 application 中设置上面代码的时候, 千万不要设置在主进程中. 否则无效.

融云 IMkit 拦截或监听所有发送消息

IM即时通讯大神庵 发表了文章 • 0 个评论 • 554 次浏览 • 2020-12-02 16:16 • 来自相关话题

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.具体的方法是 Ro... ...查看全部

微信截图_20201202160910.png

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.

在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.

具体的方法是 RongIM 类下的 setSendMessageListener 方法.

代码如下.

 /**
     * 设置发送消息的监听。
     *
     * @param listener 发送消息的监听。
     */
    RongIM.setSendMessageListener(new OnSendMessageListener() {
           @Override
         public Message onSend(Message message) {
                 // 发送消息之前会走此方法. message 为要发送的消息,
                 // 如果返回 null 的话, 就不会发送此消息了.
                 return message;
         }
         @Override
        public boolean onSent(Message message, SentMessageErrorCode sentMessageErrorCode) {
                 发送成功之后会走方法. 返回 true , 就会走 SDK 的后续逻辑. 返回 false 就拦截了.         return true;
             }
    }).


【社区精华|持续更新】收录本社区精华内容,手把手教学IM/RTC开发!

IM即时通讯admin 发表了文章 • 8 个评论 • 778 次浏览 • 2020-12-07 14:41 • 来自相关话题

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。Android篇融云即时通讯SDK集成 — 通知检查融云 IM SDK 集成 —- 刷新会话界面... ...查看全部

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。

微信截图_20201207144054.png

Android篇

融云即时通讯SDK集成 — 通知检查

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

Android 端如何添加自定义表情

解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

融云清空历史消息 Android 端

唠一唠融云的消息扩展功能

融云 IMkit 拦截或监听所有发送消息

融云如何把图片消息的图片上传到自己的文件服务器

唠一唠融云 VIVO push 无法跳转的解决方案

融云即时通讯SDK集成 — 定制UI(一) ——会话界面小改动

融云即时通讯SDK集成 — 定制UI(二) ——添加自定义表情库

融云即时通讯SDK集成 — 定制UI(三) ——兼容Android Q

融云如何把图片消息的图片上传到自己的文件服务器

融云即时通讯SDK集成 — 华为推送的点击跳转处理

带你实现女朋友欲罢不能的 App

Flutter 集成融云 sdk

配置融云SDK的自签证书

自定义消息 包含 list 数组

关于融云聊天室KV 值的正确使用

融云 IM SDK 转 AndroidX

融云即时通讯SDK集成 — 国内厂商推送集成踩坑篇(Android平台)

在融云 IMkit 会话界面基础上添加消息已读未读

融云聊天室属性 kv

融云 ConversationListFragment 会话列表添加头部布局

融云即时通讯SDK集成 — FCM推送集成指南(Android平台)

融云集成之避坑指南-Android推送篇

融云IMKit 动态删除或添加plugin 的实现


iOS篇

iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4

如何隐藏融云输入框语音按钮

给融云的输入框上方加个功能按钮,怎么整?

融云 IM SDK 如何插入消息

集成融云 IMLib 时,如何实现一套类似于 IMKit 的用户信息管理机制

为融云聊天页面的输入框添加 Placeholder

30 分钟集成融云 IM 即时通讯

简单介绍融云 imkit 包含功能

融云的聊天页面在 iOS14 出现崩溃的解决办法

融云聊天页面长按消息后“翻译”功能的实现方法

使用融云 IM 点击最近聊天记录时跳转到 @ 自己的消息

如何设置融云用户信息

自定义融云会话列表 cell 选中背景

融云 IMKit 音频录制参数

融云会话页面刷新不及时问题

融云 Flutter IM SDK 解析

关于融云 SDK 在使用 p8 证书的坎坷~

融云 SDK 如何实现群组操作

如何利用融云 IMLib 来实现一个阅后即焚功能

干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执


Web篇

作为小白接融云 IM SDK 新路体验~

微信小程序集成融云 SDK (即时通讯) 集成必备条件

Web 端使用融云 SDK 集成实现滑动加载历史消息

融云IM SDK web 端集成 — 表情采坑篇

融云 Web SDK 如何实现表情的收发 ?

集成融云小程序 SDK 遇到的问题

使用融云 Web SDK 撤回消息

融云 RTC SDK 集成实现直播,趟坑之旅~~~

融云 Web SDK 删除历史消息

集成融云小程序 SDK 遇到的问题

Web 端集成融云 SDK 如何发送正确图片消息给移动端展示?

使用融云 IM SDK 实现 H5 直播聊天

WebRTC 实现实时音视频技术研究

融云发送语音消息

融云 CallLib 集成遇到的问题

结合融云 WebSDK 了解 WebSocket 基本原理

集成融云 Web 音视频通话踩坑之旅

SDK 兼容 JSON

融云 IM SDK 发送语音消息

集成融云 IM 问题总结

融云 Web SDK 如何实现只有一个设备登入

融云 Web 播放声音 — Flash 篇 (播放 AMR、WAV)

融云 IM 那些事儿

融云 AMR(Aduio) 播放 AMR 格式 Base64 码音频


社区福利

【领取见面礼】限量 100份 GeekOnline加油包!等你来拿

【有奖调研】Geek Online 2020 编程挑战赛参赛调研

【征稿活动】Geek Online 社区第一期投稿激励计划已启动!


GeekOnline编程挑战赛

Geek Online 2020 编程挑战赛官网

重磅!Geek Online 2020 编程挑战赛来了!

Geek Online 2020 编程挑战赛 GitHub 仓库

2 个月激烈角逐,15 支队伍突围决赛路演!Geek Online 2020 编程挑战赛完美收官!

一张图回顾 Geek Online 2020 编程挑战赛精彩瞬间!

“这些项目不是什么赚大钱的项目,但是它们足够有趣。”丨关于 Geek Online 2020 编程挑战赛,选手们如是说

融云 CTO 杨攀: Geek Online 2020 编程挑战赛 让开发者站上 C 位

【参赛攻略】你想了解的Geek Online 2020 编程挑战赛常见问题这里都有!

【融云集成常见问题整理】Geek Online 2020 编程挑战赛选手提问整理


求职招聘

【招聘】寻一枚熟悉融云IM的开发工程师,坐标合肥,待遇从优

和50万优质程序员一起成长——程序员客栈招聘

持续更新....

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 678 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 658 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


融云如何把图片消息的图片上传到自己的文件服务器

IM即时通讯大神庵 发表了文章 • 0 个评论 • 583 次浏览 • 2020-12-02 16:16 • 来自相关话题

我们使用融云开发的项目, 但我们有一个需求是, 把图片不要上传到融云的服务器, 而是自己的服务器.于是就咨询了一下技术支持.被告知有一个接口方法完全可以满足我们的需求.       &nbs... ...查看全部

我们使用融云开发的项目, 但我们有一个需求是, 把图片不要上传到融云的服务器, 而是自己的服务器.于是就咨询了一下技术支持.

微信截图_20201202160709.png

被告知有一个接口方法完全可以满足我们的需求.

             ImageMessage imageMessage = ImageMessage.obtain(Uri.parse(FILEPATH), Uri.parse(FILEPATH));
        configSendMessage(imageMessage);
        Message message = Message.obtain(mTargetId,mConversationType,imageMessage);
        RongIM.getInstance().sendImageMessage(message, "pushcontent", "pushdata",
                new RongIMClient.SendImageMessageWithUploadListenerCallback() {
                    @Override
                    public void onAttached(Message message, RongIMClient.UploadImageStatusListener watcher) {
                            // 这里是自己上传图片的逻辑, 图片的路径可以通过 message 中进行获取. 
                            //watcher 这个参数主要是用于把自己的上传状态同步给 sdk. 这样我们就可以使用 sdk 内部的默认逻辑, 包括界面.
                    }
                    @Override
                    public void onError(Message message, RongIMClient.ErrorCode code) {
                    }
                    @Override
                    public void onSuccess(Message message) {
                    }
                    @Override
                    public void onProgress(Message message, int progress) {
                    }
                });

这样就可以继续使用 IMkit 的界面以及其他逻辑, 只是在 onAttached 中编写我们的上传逻辑并使用 watcher 上传的进度、成功或失败状态同步给 sdk 即可.


融云 IMkit 拦截或监听所有发送消息

IM即时通讯大神庵 发表了文章 • 0 个评论 • 554 次浏览 • 2020-12-02 16:16 • 来自相关话题

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.具体的方法是 Ro... ...查看全部

微信截图_20201202160910.png

最近集成融云 IMkit 的 SDK, 有一个需求是要监听所有发出去的消息, 根据消息类型拦截或者进行修改.

在官方文档上着了一遍, 都没有找到, 偶然在看 API 文档的时候看见了一个监听然后做了尝试, 是可以满足需求的, 所以再次记录一下.

具体的方法是 RongIM 类下的 setSendMessageListener 方法.

代码如下.

 /**
     * 设置发送消息的监听。
     *
     * @param listener 发送消息的监听。
     */
    RongIM.setSendMessageListener(new OnSendMessageListener() {
           @Override
         public Message onSend(Message message) {
                 // 发送消息之前会走此方法. message 为要发送的消息,
                 // 如果返回 null 的话, 就不会发送此消息了.
                 return message;
         }
         @Override
        public boolean onSent(Message message, SentMessageErrorCode sentMessageErrorCode) {
                 发送成功之后会走方法. 返回 true , 就会走 SDK 的后续逻辑. 返回 false 就拦截了.         return true;
             }
    }).


融云清空历史消息 Android 端

IM即时通讯大神庵 发表了文章 • 0 个评论 • 598 次浏览 • 2020-12-02 16:16 • 来自相关话题

融云清空历史消息 Android 端先调用获取历史消息。/** * 根据会话类型的目标 Id,回调方式获取N条历史消息记录。 * * @param conversationType 会话类型。不支持传入&nbs... ...查看全部

融云清空历史消息 Android 端

微信截图_20201202161404.png

  1. 先调用获取历史消息。

/**
* 根据会话类型的目标 Id,回调方式获取N条历史消息记录。
*
* @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM。
* @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
* @param oldestMessageId  最后一条消息的 Id,获取此消息之前的 count 条消息,没有消息第一次调用应设置为:-1。
* @param count            要获取的消息数量。
* @param callback         获取历史消息记录的回调,按照时间顺序从新到旧排列。
*/
public void getHistoryMessages(Conversation.ConversationType conversationType, String targetId, int oldestMessageId, int count, RongIMClient.ResultCallback<List<Message>> callback) {
RongIMClient.getInstance().getHistoryMessages(conversationType, targetId, oldestMessageId, count, callback);
}
  1. 再调用删除的接口。

/**
* 删除指定的一条或者一组消息,回调方式获取是否删除成功。
*
* @param messageIds 要删除的消息 Id 数组。
* @param callback   是否删除成功的回调。
*/
public void deleteMessages(final int[] messageIds, final RongIMClient.ResultCallback<Boolean> callback) {
RongIMClient.getInstance().deleteMessages(messageIds, new RongIMClient.ResultCallback<Boolean>() {
@Override
public void onSuccess(Boolean bool) {
if (bool)
RongContext.getInstance().getEventBus().post(new Event.MessageDeleteEvent(messageIds));
if (callback != null)
callback.onSuccess(bool);
}
@Override
public void onError(RongIMClient.ErrorCode e) {
if (callback != null)
callback.onError(e);
}
});
}

清除远端消息调接口

  1. cleanRemote 传 true ,时间戳传入当前的时间戳。

/**

  • 删除指定时间戳之前的消息,可选择是否同时删除服务器端消息

  • 此方法从服务器端清除历史消息,但是必须先开通历史消息云存储功能。

  • 根据会话类型和 TargetId 清除某一会话指定时间戳之前的本地数据库消息(服务端历史消息),

  • 清除成功后只能从本地数据库(服务端)获取到该时间戳之后的历史消息。

*
* @param conversationType 会话类型。
* @param targetId         会话目标ID。
* @param recordTime       清除消息截止时间戳,【0 ~ 当前时间的 Unix 时间戳】。
* @param cleanRemote      是否删除服务器端消息
* @param callback         清除消息的回调。
*/
public void cleanHistoryMessages
  1. 如果界面没有刷新,重新进入看下是否全部删除了。

您先试下是否可以删除,如果可以删除,但是界面没有刷新刷新的话,您可以调用 ConversationFragment 的

getMessageAdapter
方法,然后

mListAdapter.removeAll();

mListAdapter.notifyDataSetChanged();
试下。


唠一唠融云的消息扩展功能

IM即时通讯大神庵 发表了文章 • 0 个评论 • 598 次浏览 • 2020-12-02 16:16 • 来自相关话题

在使用融云 SDK 的过程中,由于定制化程度过高,其中有一项业务的实现是需要实现“红包”功能,但是实现的流程比较复杂:自定义红包消息 MoneyMessage ,然后 A 用户发送给B 用户,B 用户在点击之后,同样发送一个自定义通知消息给 A ,用来修改A ... ...查看全部

微信截图_20201202161542.png

在使用融云 SDK 的过程中,由于定制化程度过高,其中有一项业务的实现是需要实现“红包”功能,但是实现的流程比较复杂:

  • 自定义红包消息 MoneyMessage ,然后 A 用户发送给B 用户,B 用户在点击之后,同样发送一个自定义通知消息给 A ,用来修改A 用户的界面。

上述流程看上去比较简单,但是实现起来就会比较复杂,并且并不灵活,于是跟融云技术反馈此问题之后,在从 4.0.3 版本开始支持单条消息扩展信息设置功能,用 消息扩展功能实现就会无比简单。

消息扩展功能的本质是在Message 类中添加了扩展功能;

 /**
     * 设置是否可以包含扩展信息
     *
     * @param canIncludeExpansion 是否可以包含扩展信息
     */
   public void setCanIncludeExpansion(boolean canIncludeExpansion);
  /**
  * 消息扩展信息列表
  *
 * @return 消息扩展信息列表
 */
public Map<String, String> getExpansion();
 /**
* 设置消息扩展信息列表
* 扩展信息只支持单聊和群组,其它会话类型不能设置扩展信息。*
* @param expansion 消息扩展信息列表
*/
 public void setExpansion(HashMap<String, String> expansion);

可以调用 RongIMClient.getInstance().updateMessageExpansion(); 来设置消息扩展属性;
可以调用 RongIMClient.getInstance().removeMessageExpansion();来删除消息扩展属性;
当然监听消息属性可以设置:

   RongIMClient.getInstance().setMessageExpansionListener(new RongIMClient.MessageExpansionListener() {
        @Override
        public void onMessageExpansionUpdate(Map<String, String> expansion, Message message) {
        }
        @Override
        public void onMessageExpansionRemove(List<String> keyArray, Message message) {
        }
    });

来实时监听消息属性的变化,这样可以动态实现红包点击接受的功能,或者礼物场景的实现

融云即时通讯SDK集成 — 定制UI(二) ——添加自定义表情库

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 564 次浏览 • 2020-12-02 16:03 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面.)当然这些已经集成了UI的sdk并不能完全满足一个产品的需求, 所以这篇文章跟大家讲下如何添加一套自定义的表情包.

效果如下哈:

QQ20201112-0.jpg

虽然这里有点难看了哈哈, 不过是为了给大家展示方法嘛, 就不管那么多了. 可以看到底下除了默认的emoji的表情包, 还多了一个tab, 我是从QQ表情搞了一套, 直接就加在这里了. 一个可爱的小猪猪🐷哈哈哈.

添加步骤

需要改动的有这么几个类:

AndroidEmoji: 控制emoji图标资源, 编码, 以及相应展示的类
RongExtension: 会话界面除去聊天气泡与title bar的整个下方输入区域
IEmoticonTab: 表情tab
DefaultExtensionModule: 表情tab的上层控件.

对于AndroidEmoji这个类, 可以直接照抄, 把资源文件替换成自己准备好的图标, 以及编码/描述

public class ConversationListAdapter extends BaseAdapter<UIConversation> {
    private final static String TAG = "ConversationListAdapter";
    LayoutInflater mInflater;
    Context mContext;
    @Override
    public long getItemId(int position) {
        UIConversation conversation = getItem(position);
        if (conversation == null)
            return 0;
        return conversation.hashCode();
    }
    protected class ViewHolder {
        public View layout;
        public View leftImageLayout;
        public View rightImageLayout;
        public View leftUnReadView;
        public View rightUnReadView;
        public AsyncImageView leftImageView;
        public TextView unReadMsgCount;
        public ImageView unReadMsgCountIcon;
        public AsyncImageView rightImageView;
        public TextView unReadMsgCountRight;
        public ImageView unReadMsgCountRightIcon;
        public ProviderContainerView contentView;
    }
    public ConversationListAdapter(Context context) {
        super();
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
    }
    public int findGatheredItem(Conversation.ConversationType type) {
        int index = getCount();
        int position = -1;
        while ((index-- > 0)) {
            UIConversation uiConversation = getItem(index);
            if (uiConversation.getConversationType().equals(type)) {
                position = index;
                break;
            }
        }
        return position;
    }
    public int findPosition(Conversation.ConversationType type, String targetId) {
        int index = getCount();
        int position = -1;
        while (index-- > 0) {
            if (getItem(index).getConversationType().equals(type)
                    && getItem(index).getConversationTargetId().equals(targetId)) {
                position = index;
                break;
            }
        }
        return position;
    }
    @Override
    protected View newView(Context context, int position, ViewGroup group) {
        View result = mInflater.inflate(R.layout.rc_item_conversation, null);
        ViewHolder holder = new ViewHolder();
        holder.layout = findViewById(result, R.id.rc_item_conversation);
        holder.leftImageLayout = findViewById(result, R.id.rc_item1);
        holder.rightImageLayout = findViewById(result, R.id.rc_item2);
        holder.leftUnReadView = findViewById(result, R.id.rc_unread_view_left);
        holder.rightUnReadView = findViewById(result, R.id.rc_unread_view_right);
        holder.leftImageView = findViewById(result, R.id.rc_left);
        holder.rightImageView = findViewById(result, R.id.rc_right);
        holder.contentView = findViewById(result, R.id.rc_content);
        holder.unReadMsgCount = findViewById(result, R.id.rc_unread_message);
        holder.unReadMsgCountRight = findViewById(result, R.id.rc_unread_message_right);
        holder.unReadMsgCountIcon = findViewById(result, R.id.rc_unread_message_icon);
        holder.unReadMsgCountRightIcon = findViewById(result, R.id.rc_unread_message_icon_right);
        result.setTag(holder);
        return result;
    }
    @Override
    protected void bindView(View v, int position, final UIConversation data) {
        ViewHolder holder = (ViewHolder) v.getTag();
        if (data == null) {
            return;
        }
        /*通过会话类型,获得对应的会话provider.ex: PrivateConversationProvider*/
        IContainerItemProvider provider = RongContext.getInstance().getConversationTemplate(data.getConversationType().getName());
        if (provider == null) {
            RLog.e(TAG, "provider is null");
            return;
        }
        View view = holder.contentView.inflate(provider);
        provider.bindView(view, position, data);
        //设置背景色
        if (data.isTop())
            holder.layout.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.rc_item_top_list_selector));
        else
            holder.layout.setBackgroundDrawable(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.rc_default_group_portrait;
        } else if (data.getConversationType().equals(Conversation.ConversationType.DISCUSSION)) {
            defaultId = R.drawable.rc_default_discussion_portrait;
        } else {
            defaultId = R.drawable.rc_default_portrait;
        }
        // 1:图像靠左显示。2:图像靠右显示。3:不显示图像。
        if (tag.portraitPosition() == 1) {
            holder.leftImageLayout.setVisibility(View.VISIBLE);
            holder.leftImageLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemClick(v, data);
                }
            });
            holder.leftImageLayout.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemLongClick(v, data);
                    return true;
                }
            });
            if (data.getConversationGatherState()) {
                holder.leftImageView.setAvatar(null, defaultId);
            } else {
                if (data.getIconUrl() != null) {
                    holder.leftImageView.setAvatar(data.getIconUrl().toString(), defaultId);
                } else {
                    holder.leftImageView.setAvatar(null, defaultId);
                }
            }
            if (data.getUnReadMessageCount() > 0) {
                holder.unReadMsgCountIcon.setVisibility(View.VISIBLE);
                setUnReadViewLayoutParams(holder.leftUnReadView, data.getUnReadType());
                if (data.getUnReadType().equals(UIConversation.UnreadRemindType.REMIND_WITH_COUNTING)) {
                    if (data.getUnReadMessageCount() > 99) {
                        holder.unReadMsgCount.setText(mContext.getResources().getString(R.string.rc_message_unread_count));
                    } else {
                        holder.unReadMsgCount.setText(Integer.toString(data.getUnReadMessageCount()));
                    }
                    holder.unReadMsgCount.setVisibility(View.VISIBLE);
                    holder.unReadMsgCountIcon.setImageResource(R.drawable.rc_unread_count_bg);
                } else {
                    holder.unReadMsgCount.setVisibility(View.GONE);
                    holder.unReadMsgCountIcon.setImageResource(R.drawable.rc_unread_remind_list_count);
                }
            } else {
                holder.unReadMsgCountIcon.setVisibility(View.GONE);
                holder.unReadMsgCount.setVisibility(View.GONE);
            }
            holder.rightImageLayout.setVisibility(View.GONE);
        } else if (tag.portraitPosition() == 2) {
            holder.rightImageLayout.setVisibility(View.VISIBLE);
            holder.rightImageLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemClick(v, data);
                }
            });
            holder.rightImageLayout.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if (mOnPortraitItemClick != null)
                        mOnPortraitItemClick.onPortraitItemLongClick(v, data);
                    return true;
                }
            });
            if (data.getConversationGatherState()) {
                holder.rightImageView.setAvatar(null, defaultId);
            } else {
                if (data.getIconUrl() != null) {
                    holder.rightImageView.setAvatar(data.getIconUrl().toString(), defaultId);
                } else {
                    holder.rightImageView.setAvatar(null, defaultId);
                }
            }
            if (data.getUnReadMessageCount() > 0) {
                holder.unReadMsgCountRightIcon.setVisibility(View.VISIBLE);
                setUnReadViewLayoutParams(holder.rightUnReadView, data.getUnReadType());
                if (data.getUnReadType().equals(UIConversation.UnreadRemindType.REMIND_WITH_COUNTING)) {
                    holder.unReadMsgCount.setVisibility(View.VISIBLE);
                    if (data.getUnReadMessageCount() > 99) {
                        holder.unReadMsgCountRight.setText(mContext.getResources().getString(R.string.rc_message_unread_count));
                    } else {
                        holder.unReadMsgCountRight.setText(Integer.toString(data.getUnReadMessageCount()));
                    }
                    holder.unReadMsgCountRightIcon.setImageResource(R.drawable.rc_unread_count_bg);
                } else {
                    holder.unReadMsgCount.setVisibility(View.GONE);
                    holder.unReadMsgCountRightIcon.setImageResource(R.drawable.rc_unread_remind_without_count);
                }
            } else {
                holder.unReadMsgCountIcon.setVisibility(View.GONE);
                holder.unReadMsgCount.setVisibility(View.GONE);
            }
            holder.leftImageLayout.setVisibility(View.GONE);
        } else if (tag.portraitPosition() == 3) {
            holder.rightImageLayout.setVisibility(View.GONE);
            holder.leftImageLayout.setVisibility(View.GONE);
        } else {
            throw new IllegalArgumentException("the portrait position is wrong!");
        }
        MessageContent content = data.getMessageContent();
        if (content != null && content.isDestruct()) {
            RongIMClient.getInstance().getMessage(data.getLatestMessageId(), new RongIMClient.ResultCallback<Message>() {
                @Override
                public void onSuccess(Message message) {
                    if (message == null) {
                        EventBus.getDefault().post(new Event.MessageDeleteEvent(data.getLatestMessageId()));
                    }
                }
                @Override
                public void onError(RongIMClient.ErrorCode e) {
                }
            });
        }
    }
    protected void setUnReadViewLayoutParams(View view, UIConversation.UnreadRemindType type) {
        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        Context context = view.getContext();
        if (type == UIConversation.UnreadRemindType.REMIND_WITH_COUNTING) {
            params.width = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_18);
            params.height = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_18);
            params.leftMargin = (int) mContext.getResources().getDimension(R.dimen.rc_dimen_size_44);
            params.topMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_5);
        } else {
            params.width = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_9);
            params.height = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_9);
            params.leftMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_50);
            params.topMargin = (int) context.getResources().getDimension(R.dimen.rc_dimen_size_7);
        }
        view.setLayoutParams(params);
    }
    private OnPortraitItemClick mOnPortraitItemClick;
    public interface OnPortraitItemClick {
        void onPortraitItemClick(View v, UIConversation data);
        boolean onPortraitItemLongClick(View v, UIConversation data);
    }
    public void setOnPortraitItemClick(OnPortraitItemClick onPortraitItemClick) {
        this.mOnPortraitItemClick = onPortraitItemClick;
    }
}

集成DefaultExtensionModule, 实现MyExtensionModule, 重写getEmoticonTabs()方法, 为每个图标设置监听.

@Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        MyEmoticonTab myEmoticonTab =new MyEmoticonTab();
        myEmoticonTab.setOnItemClickListener(new IEmojiItemClickListener() {
            @Override
            public void onEmojiClick(String emoji) {
                EditText editText = MyExtensionModule.this.mEditText;
                if (editText != null) {
                    int start = editText.getSelectionStart();
                    editText.getText().insert(start, emoji);
                }
            }
            @Override
            public void onDeleteClick() {
                EditText editText = MyExtensionModule.this.mEditText;
                if (editText != null) {
                    editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
                }
            }
        });
        emoticonTabs.add(myEmoticonTab);
        return emoticonTabs;
    }

继承RongExtension, 实现MyRongExtension, 然后获取到editText, 为其设置监听, 代码如下:

public class MyRongExtension extends RongExtension {
    EditText mEditText;
    public MyRongExtension(Context context) {
        super(context);
        tryAddTextChangedAction();
    }
    public MyRongExtension(Context context, AttributeSet attrs) {
        super(context, attrs);
        tryAddTextChangedAction();
    }
    private void tryAddTextChangedAction() {
        mEditText = getInputEditText();
        TextWatcher textWatcher = new TextWatcher() {
            private int start;
            private int count;
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                this.start = start;
                this.count = count;
            }
            @Override
            public void afterTextChanged(Editable s) {
                // 这块的检验规则是纯emoji的, 加其他表情的话直接给他去掉, 或者自己写规则。
//                if (QQEmoji.isQQEmoji(s.subSequence(start, start + count).toString())) {
                    mEditText.removeTextChangedListener(this);
                    String resultStr = QQEmoji.replaceEmojiWithText(s.toString());
                    mEditText.setText(QQEmoji.ensure(resultStr), TextView.BufferType.SPANNABLE);
                    mEditText.setSelection(mEditText.getText().length());
                    mEditText.addTextChangedListener(this);
//                }
            }
        };
        mEditText.addTextChangedListener(textWatcher);
    }
}

这样一来, 就大功告成啦. 添加好了一套属于自己的表情包!

融云即时通讯SDK集成 — 华为推送的点击跳转处理

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 238 次浏览 • 2020-12-02 15:58 • 来自相关话题

1.背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地... ...查看全部

1.背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 基础的IM聊天, 群组聊天等功能集成在另一篇博客中已经讲过, 这里就先不说了. 之前的一篇文章已经讲过, 这篇文章重点讲下华为推送过来的通知栏点击事件, 这小小的一件事, 可耗费了不少的精力.

2. 融云家SDK接入厂商推送指南

这个他家讲的挺清楚了, 都近似手把手的教了, 那我也就不在这浪费键盘寿命了(偷个懒hhhhh).给他家官方文档往这儿一贴好了: https://docs.rongcloud.cn/v4/views/im/ui/guide/private/notify/push/mi.html. 大家注意一下他家华为厂商推送的文档分2.x和4.x哈, 对应的是他家旧版/新版SDK中接入的厂商的新/旧版本推送包.我这里之前是踩过坑的, 推荐大家还是用他家的4.x的sdk包集成, 里边集成的是华为新版本的推送包. 因为华为已经在逐渐弃用旧版本的推送包了, 所以不想以后麻烦再迭代的话, 还是直接上新版本比较好, 因为现在已经有接入旧版本包的app在某些低版本华为Rom上收不到推送了, 还是谨慎一点比较好.

3. 华为推送通知点击跳转自定义

这里我们所指的跳转是, 点击通知栏到达的华为推送通知后, 调起我们的app, 跳到相应的Activity的. 因为我们做的是即时通讯App, 所以点击别人发来的消息推送通知后, 当然是跳到和该用户聊天的会话页面, 但就是这样一个小业务, 实现起来还真有点麻烦. 所以这里也算是给大家踩踩坑啦.

首先是点击跳转的逻辑, 通过和融云技术支持联系以及查看他们sdk的代码, 得知他们的推送都是走了一个叫PushMessageReceiver的类, 开发者可以注册并继承该类, 重写onNotificationMessageClicked()方法, 显而易见这就是对通知消息的监听. 可这也是第一个坑, 华为因为平台的限制, 推送通知的点击事件根本不走这里. 准确的说是华为和Oppo都因为各自平台的限制, 不走这里. 所以当我兴致勃勃重写了这个方法准备一次通过的时候, 点击后直接走到了会话列表界面, 而非会话界面.

这是因为华为平台点击跳转是需要配一个intent, 在融云的后台, 如图所示:
QQ20201102-111109@2x.png

这里的intent即为通知栏点击跳转后的隐式启动相应activity的那个intent.

获取某Activity相应intent的代码, 可把intent给打到log中:

  1. Intent intent =newIntent(Intent.ACTION_VIEW,

  2.                Uri.parse("wonderfullpush://com.wonderfull.android.push/notification?action=your parameter"));

  3.        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

  4.        intent.setAction(Intent.ACTION_VIEW);

  5.        String intnetUri = intent.toUri(Intent.URI_INTENT_SCHEME);

  6.        Log.d("hwpush","intnetUri="+ intnetUri);

生成的intent大概是这样的格式:

  1. intent://com.wonderfull.android.push/notification?action=$action#Intent;scheme=wonderfullpush;action=android.intent.action.VIEW;launchFlags=0x10000000;end

不过这块还有坑, 还不能直接把装会话界面的activity的intent直接写在这里, 因为这个会话界面去的究竟是哪个对话者的界面, 相应userId的参数是传不到的. 在这里我尝试获取过intent里边的数据, 是空的.

和融云的技术支持团队确认后, 找到了解决方案. 只有intent走到入口类, 才能拿的到通知里携带的数据, 我app的闪屏页, 也就是SplashActivity, 在融云后台把自定义点击跳转intent配成SplashActivity的. 然后在这里把intent的数据拿到, 再一次调用融云的接口跳转到相应的会话界面就OK了.

等拿到这个intent的后, 又是来了一点点小麻烦. 根据融云的文档, 我在入口Activity(闪屏页, SplashActivity)这里接收到了intent, 从intent里边取相应的数据:

这个intent中给的数据包含了转义符:

{"rc":"{\"conversationType\":\"1\",\"targetId\":\"userid8\",\"sourceType\":\"0\",\"fromUserId\":\"userid8\",\"objectName\":\"RC:TxtMsg\",\"id\":\"BLCG-G8TC-U7E6-KV7P\",\"tId\":\"doctorid3\"}"}
String jsonStr = "{\"rc\":\"{\\\"conversationType\\\":\\\"1\\\",\\\"targetId\\\":\\\"userid8\\\",\\\"sourceType\\\":\\\"0\\\",\\\"fromUserId\\\":\\\"userid8\\\",\\\"objectName\\\":\\\"RC:TxtMsg\\\",\\\"id\\\":\\\"BLCG-G8TC-U7E6-KV7P\\\",\\\"tId\\\":\\\"doctorid3\\\"}\"}";        String fixStr1 = jsonStr.replace("\\", "");        String fixStr2 = fixStr1.replace("\"rc\":\"", "\"rc\":" );        String result = fixStr2.replace("\"}\"", "\"}");        Log.d(TAG, "result jsonStr: "+ result);        JSONObject jsonObject;        try {            jsonObject = new JSONObject(result);            String options = jsonObject.getString("rc");            JsonObject object = (JsonObject) new JsonParser().parse(options);            String targetId = object.getAsJsonObject().get("targetId").getAsString();            Log.d(TAG, "analyse json targetId: " + targetId);        } catch (JSONException e) {            e.printStackTrace();        }

我选择了自行处理字符串, 拿到了相应的targetId, 从我的闪屏页跳转到相应的会话界面去. 这样需求就搞定了.


融云即时通讯SDK集成 — 定制UI(一) ——会话界面小改动

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 586 次浏览 • 2020-12-02 15:58 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面.)当然这些已经集成了UI的sdk并不能完全满足一个产品的需求, 所以这篇文章主要跟大家讲下如何对他们家的UI进行简单的自定义.

融云SDK接入会话列表/会话界面

大家集成的话可以直接按照他们家的快速集成的步骤走, 包含了集成会话列表以及会话界面. 这里是链接: 快速集成SDK (https://docs.rongcloud.cn/v4/views/im/noui/guide/quick/include/android.html)

接入之后, 可以按照合适的逻辑跳转入这两个基本的界面, 如图所示:
会话列表:
QQ20201102-165916@2x.png会话界面
QQ20201102-165944@2x.png

可以看到我们的app中现在拥有了默认会话列表和会话界面UI, 可以使用基本的功能了. 但是这些基本的UI虽说不上难看, 但是也够不上精美. 所以UI这块还是需要我们自行来做一些定制的.

为了给大家演示, 我这里改的比较夸张了一点, 十分丑陋, 但直观哈哈哈.

3F7310C0-9826-4505-9CE8-56B1EE4427EA.png

如图我直接更改了两边发送者&接收者的字体颜色,字体大小,字体样式. 也更改了双方的聊天气泡.

修改普通文字消息类型的消息, 直接继承了TextMessageItemProvider. 把父类里边所有的代码都复制进来, 然后在bindView()的时候做修改

可以看到我把样式随便改了一下. 两个方向的气泡都改成了箭头左向的.

@Override
    public void bindView(final View v, int position, TextMessage content, final UIMessage data) {
        ViewHolder holder = (ViewHolder) v.getTag();
        holder.receiverFire.setTag(data.getUId());
        if (data.getMessageDirection() == Message.MessageDirection.SEND) {
            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_right);
        } else {
            holder.message.setBackgroundResource(R.drawable.rc_ic_bubble_left);
        }
        if (content.isDestruct()) {
            bindFireView(v, position, content, data);
        } else {
            holder.sendFire.setVisibility(View.GONE);
            holder.receiverFire.setVisibility(View.GONE);
            holder.unRead.setVisibility(View.GONE);
            holder.message.setVisibility(View.VISIBLE);
            final AutoLinkTextView textView = holder.message;
            processTextView(v, position, content, data, textView);
        }
    }

做完改动, 还需要给这个类添加这样的注解才能绑定TextMessage的渲染:

@ProviderTag(
        messageContent = TextMessage.class,
        showReadState = true
)
    public class MyTextMessageItemProvider extends TextMessageItemProvider

然后记得在init 我们SDK之后, 注册一下这个Provider.

 RongIM.init(this, APP_KEY);
    RongIM.registerMessageTemplate(new MyTextMessageItemProvider());

这样所有收到的类型为TextMessage的消息, 都会按照我这里定义的TextMessageItemProvider来做展示了. 其他类型的消息也是一样的, 语音, 文件, 位置消息. 只要想改UI, 本质上都是集成相应的MessageItemProvider, 然后重写bindView()方法.


android vivox27聊天界面点击拍照直接退出

回复

IM即时通讯admin 回复了问题 • 1 人关注 • 1 个回复 • 244 次浏览 • 2020-11-20 09:56 • 来自相关话题

【社区精华|持续更新】收录本社区精华内容,手把手教学IM/RTC开发!

IM即时通讯admin 发表了文章 • 8 个评论 • 778 次浏览 • 2020-12-07 14:41 • 来自相关话题

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。Android篇融云即时通讯SDK集成 — 通知检查融云 IM SDK 集成 —- 刷新会话界面... ...查看全部

本文收录了GeekOnline社区精华内容,希望帮助社区开发者学习IM+RTC知识,解答疑惑。赠人玫瑰,手有余香,如您有不错的内容需要收录,欢迎在在评论区投稿回复。

微信截图_20201207144054.png

Android篇

融云即时通讯SDK集成 — 通知检查

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

Android 端如何添加自定义表情

解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

融云清空历史消息 Android 端

唠一唠融云的消息扩展功能

融云 IMkit 拦截或监听所有发送消息

融云如何把图片消息的图片上传到自己的文件服务器

唠一唠融云 VIVO push 无法跳转的解决方案

融云即时通讯SDK集成 — 定制UI(一) ——会话界面小改动

融云即时通讯SDK集成 — 定制UI(二) ——添加自定义表情库

融云即时通讯SDK集成 — 定制UI(三) ——兼容Android Q

融云如何把图片消息的图片上传到自己的文件服务器

融云即时通讯SDK集成 — 华为推送的点击跳转处理

带你实现女朋友欲罢不能的 App

Flutter 集成融云 sdk

配置融云SDK的自签证书

自定义消息 包含 list 数组

关于融云聊天室KV 值的正确使用

融云 IM SDK 转 AndroidX

融云即时通讯SDK集成 — 国内厂商推送集成踩坑篇(Android平台)

在融云 IMkit 会话界面基础上添加消息已读未读

融云聊天室属性 kv

融云 ConversationListFragment 会话列表添加头部布局

融云即时通讯SDK集成 — FCM推送集成指南(Android平台)

融云集成之避坑指南-Android推送篇

融云IMKit 动态删除或添加plugin 的实现


iOS篇

iOS 基于实时音视频 SDK 实现屏幕共享功能——1

iOS 基于实时音视频 SDK 实现屏幕共享功能——2

iOS 基于实时音视频 SDK 实现屏幕共享功能——3

iOS 基于实时音视频 SDK 实现屏幕共享功能——4

如何隐藏融云输入框语音按钮

给融云的输入框上方加个功能按钮,怎么整?

融云 IM SDK 如何插入消息

集成融云 IMLib 时,如何实现一套类似于 IMKit 的用户信息管理机制

为融云聊天页面的输入框添加 Placeholder

30 分钟集成融云 IM 即时通讯

简单介绍融云 imkit 包含功能

融云的聊天页面在 iOS14 出现崩溃的解决办法

融云聊天页面长按消息后“翻译”功能的实现方法

使用融云 IM 点击最近聊天记录时跳转到 @ 自己的消息

如何设置融云用户信息

自定义融云会话列表 cell 选中背景

融云 IMKit 音频录制参数

融云会话页面刷新不及时问题

融云 Flutter IM SDK 解析

关于融云 SDK 在使用 p8 证书的坎坷~

融云 SDK 如何实现群组操作

如何利用融云 IMLib 来实现一个阅后即焚功能

干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执


Web篇

作为小白接融云 IM SDK 新路体验~

微信小程序集成融云 SDK (即时通讯) 集成必备条件

Web 端使用融云 SDK 集成实现滑动加载历史消息

融云IM SDK web 端集成 — 表情采坑篇

融云 Web SDK 如何实现表情的收发 ?

集成融云小程序 SDK 遇到的问题

使用融云 Web SDK 撤回消息

融云 RTC SDK 集成实现直播,趟坑之旅~~~

融云 Web SDK 删除历史消息

集成融云小程序 SDK 遇到的问题

Web 端集成融云 SDK 如何发送正确图片消息给移动端展示?

使用融云 IM SDK 实现 H5 直播聊天

WebRTC 实现实时音视频技术研究

融云发送语音消息

融云 CallLib 集成遇到的问题

结合融云 WebSDK 了解 WebSocket 基本原理

集成融云 Web 音视频通话踩坑之旅

SDK 兼容 JSON

融云 IM SDK 发送语音消息

集成融云 IM 问题总结

融云 Web SDK 如何实现只有一个设备登入

融云 Web 播放声音 — Flash 篇 (播放 AMR、WAV)

融云 IM 那些事儿

融云 AMR(Aduio) 播放 AMR 格式 Base64 码音频


社区福利

【领取见面礼】限量 100份 GeekOnline加油包!等你来拿

【有奖调研】Geek Online 2020 编程挑战赛参赛调研

【征稿活动】Geek Online 社区第一期投稿激励计划已启动!


GeekOnline编程挑战赛

Geek Online 2020 编程挑战赛官网

重磅!Geek Online 2020 编程挑战赛来了!

Geek Online 2020 编程挑战赛 GitHub 仓库

2 个月激烈角逐,15 支队伍突围决赛路演!Geek Online 2020 编程挑战赛完美收官!

一张图回顾 Geek Online 2020 编程挑战赛精彩瞬间!

“这些项目不是什么赚大钱的项目,但是它们足够有趣。”丨关于 Geek Online 2020 编程挑战赛,选手们如是说

融云 CTO 杨攀: Geek Online 2020 编程挑战赛 让开发者站上 C 位

【参赛攻略】你想了解的Geek Online 2020 编程挑战赛常见问题这里都有!

【融云集成常见问题整理】Geek Online 2020 编程挑战赛选手提问整理


求职招聘

【招聘】寻一枚熟悉融云IM的开发工程师,坐标合肥,待遇从优

和50万优质程序员一起成长——程序员客栈招聘

持续更新....

【Android开发】如何使用融云的消息扩展

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 116 次浏览 • 2021-02-24 19:30 • 来自相关话题

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html总结文... ...查看全部

微信截图_20210224192550.png

从 【4.0.3 】版本开始,融云新增了消息扩展功能,文档如下: https://docs.rongcloud.cn/v4/views/im/noui/guide/private/msgmanage/expansion/android.html

总结文档中的几个重点:

  • 4.0.3之前的版本无法使用。

  • 消息扩展是 Message类的属性,也就是说自定义消息也可以使用。

  • 单条消息只能设置300个kv,(某些场景不可用,比如在群里发400个人可领的红包)。

  • 仅支持单聊、群聊。

  • key和value都是字符串类型,并且key不能是中文,否则会报错INVALID_PARAMETER。

  • 用IMKit的UI发送消息时需要拦截一下,设置消息扩展开关。

使用步骤:

  1. 消息的发送端必须给需要消息扩展的消息开开关,没有全局设置,只能单条消息设置。分以下两种情况:

  2. 如果使用的是 IMKit 提供的UI发送消息,在 ConversationFragment中的 onSendToggleClick 方法发送消息,在 sendMessage 前调用下面的方法

    /**
     * 设置消息扩展信息列表
     * 

扩展信息只支持单聊和群组,其它会话类型不能设置扩展信息。


     *
     * @param expansion 消息扩展信息列表
     */
    public void setExpansion(HashMap<String, String> expansion) {
        this.expansion = expansion;
    }

还需要设置 canIncludeExpansion 和 expansionDic 。

  • 如果是自己调用 RongIM 和 RongIMClient 接口发的消息,用 MessageContent 类的对象构造一个 Message 对象,然后设置 canIncludeExpansion 和 expansionDic 再调用RCIM中对应的 send 方法将消息发送出去即可。

  • 更新和删除扩展的操作可以参考官方文档。

  • 想要实时捕获消息扩展的更改和删除,就需要设置回调,在回调中更新UI。设置回调可参考官方文档。

融云官网:https://www.rongcloud.cn/

文档频道:https://docs.rongcloud.cn/v4


一把双刃剑 -- 融云即时通讯sdk中的自定义消息使用心得&指南 (下)

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 104 次浏览 • 2021-02-24 19:30 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20210224192919.png

背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).

他家的官网和文档地址: 官网:https://www.rongcloud.cn/ 文档:https://docs.rongcloud.cn/v4

这个任务当然还是落在我的头上. 我是使用的他们家的带UI的sdk,(他们家有带UI和不带UI的两种sdk, 不带UI的sdk就是只有即时通讯能力, 所有的UI都需要开发者自定实现, 带UI的sdk封装了一些基本的界面,例如会话列表, 和别人聊天的会话界面).

心得 (下)

自定义小视频消息

接上篇对自定义消息的开发心得哈.

因为融云家自带的小视频消息是需要收费的, 需要在服务端开通小视频服务后, 同时在端上做一下配置, 才可以使用小视频消息. 我一看这还得了, 想方设法收我钱呢不是. 不过他家只是对小视频类型的消息在服务端做了限制, 而不是完全不让在消息中携带视频链接. 自定义消息是随便自定义的, 那么我自定义一个小视频消息不就好啦.

大概实现思路如下:

自定义小视频消息继承MediaMessageContent,其中mLocalPath是小视频文件本地的存放路径,mMediaUrl是小视频文件上传到文件服务器后的http/https地址。

小视频的拍摄,播放我们RongCloud SDK没有接口,开发者自己实现。

当拍摄完成,发送小视频消息时使用方法

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallback callback)或者

sendMediaMessage(final Message message, final String pushContent, final String pushData, final IRongCallback.ISendMediaMessageCallbackWithUploader callback)

这两个方法的不同是后者开发者负责小视频文件的上传到指定的服务器,前者使用我们RongCloud默认的文件服务器

以上是大致步骤,小视频开发过程中可能遇到的问题,说明如下:

1.关于缩略图的处理,我们SDK没有直接上传一张图片返回一个url地址的接口,开发者可以把缩略图上传到自己的服务器,这样缩略图跟mMediaUrl类似,小视频消息展示显示缩略图时加载一张网络图片即可。

另一种缩略图处理方式类似我们SDK发送图片消息时的缩略图处理,把缩略图做base64编码,放到自定义消息体中直接传输,这种方式涉及到消息发送时把缩略图转化为base64数据和接收到消息时还原为缩略图,在我们SDK内部使用的是MessageHandler。

关于MessageHandler,我们RongCloud的每个消息都有一个MessageHandler,此前我们文档从没有介绍过这个

MessageHandler,对用户透明的,用户的自定义消息没有指定它是因为有个默认的DefaultMessageHandler。

自定义消息时可以指定自己的MessageHandler,例如图片消息的定义如下

ImageMessage.png

MessageHandler在消息发送和接收时在IPC进程中会被自动调用,它有两个方法,encodeMessage

和decodeMessage,在消息接收后调用decodeMessage时开发者可以把base64对应的数据转化为缩略图url,这样在展示缩略图时直接使用url即可。

/**
 * 解码 {@link MessageContent} 到 {@link Message} 中。
 *
 * @param message 用于存放 MessageContent 的消息实体。
 * @param content 将要被解码的 MessageContent。
 */
public abstract void decodeMessage(Message message, T content);

/**
 * 对 {@link Message} 编码。
 *
 * @param message 将要被编码的 Message 实体。
 */
public abstract void encodeMessage(Message message);

此文档包含了两个附件分别为自定义小视频消息和对应的小视频消息MessageHandler,供开发者参考

2.开发中可能还会遇到小视频文件上传时进度更新的问题,如果开发者自定义的小视频消息不继承自MediaMessageContent而是MessageContent,需要自己在UI上维护上传进度


融云 Android 端如何清空某一个会话的聊天记录

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 107 次浏览 • 2021-02-24 19:30 • 来自相关话题

融云 Android 端如何清空某一个会话的聊天记录实现须知没有开通 “历史消息云存储”,只能删除本地的数据。在融云控制台开通 “历史消息云存储功能” 后,才可以删除远端消息。实现步骤1.调用 cleanHistoryMessages 方法,cleanRemo... ...查看全部

融云 Android 端如何清空某一个会话的聊天记录

微信截图_20210224192137.png

实现须知

没有开通 “历史消息云存储”,只能删除本地的数据。在融云控制台开通 “历史消息云存储功能” 后,才可以删除远端消息。

实现步骤

1.调用 cleanHistoryMessages 方法,cleanRemote 传 true ,recordTime 传 当前的时间戳。

/**
     * 删除指定时间戳之前的消息,可选择是否同时删除服务器端消息
     * 

此方法从服务器端清除历史消息,但是必须先开通历史消息云存储功能。


     * 

根据会话类型和 TargetId 清除某一会话指定时间戳之前的本地数据库消息(服务端历史消息),
     * 清除成功后只能从本地数据库(服务端)获取到该时间戳之后的历史消息。


     *
     * @param conversationType 会话类型。
     * @param targetId         会话目标ID。
     * @param recordTime       清除消息截止时间戳,【0 ~ 当前时间的 Unix 时间戳】。
     * @param cleanRemote      是否删除服务器端消息
     * @param callback         清除消息的回调。
     */
    public void cleanHistoryMessages(final Conversation.ConversationType conversationType,
                                     final String targetId,
                                     final long recordTime,
                                     final boolean cleanRemote,
                                     final OperationCallback callback)

2.您使用的是 imlib ,还需要自行刷新您的界面。 3.您使用的是 imkit 调用 ConversationFragment 的 getMessageAdapter 方法,获取到会话界面消息展示适配器,移除数据并且刷新数据源。

mListAdapter.removeAll();
mListAdapter.notifyDataSetChanged();

4.卸载重装或者更换设备登陆后,历史消息还可以拉取到,来保证用户更换设备或者卸载重装后还可以有上下文。如果您不希望换端后再收到这些消息,您可以 提供一下 appkey 提交工单,我们帮您把这部分数据的缓存功能关闭。

Flutter Platform Channel深度解析

IM即时通讯admin 发表了文章 • 0 个评论 • 149 次浏览 • 2021-01-28 14:20 • 来自相关话题

一、简介Platform Channel 是 Flutter 端与 Platform 端制定的通信机制,由官方提供用于 Dart 和平台之间的相互通信。分为以下 3 种(1)BaseMessageChannel :用于传递字符串和半结构化的信息(在大内存数据块... ...查看全部

一、简介

Platform Channel 是 Flutter 端与 Platform 端制定的通信机制,由官方提供用于 Dart 和平台之间的相互通信。

分为下 3 种

(1)BaseMessageChannel :用于传递字符串和半结构化的信息(在大内存数据块传递的情况下使用)

(2)MethodChannel:用于传递方法调用(Method Invocation

(3)EventChannel: 用于数据流(Event Streams)的通信

 

二、消息传递与编码器

Flutter 的消息传递工具是 BinaryMessager ,通过它与 Platform 建立起通信关系,消息以二进制的格式进行传递。

1.png

如图所示 BinaryMessager 的传递需要经过 BinaryMessageHandler,BinaryMessagerHandler 是以 Channel Name 作为键值生成出来再被注册到 BinaryMessager 上的,BinaryMessageHandler 和 BinaryMessager 是一一对应的,二进制格式的消息通过消息编码器(Codec)解码为识别的信息,并传递给 Handler 进行处理。Handler 处理完后,会把结果编码为二进制格式,再通过回调函数返回结果并发送回 Flutter 端

1.编码器分类

(1)MessageCodec:BinaryCodec、StringCodec、JSONMessageCodec、StandardMessageCodec

(2)MethodCodec:JSONMethodCodec、StandardMessageCodec

2.png

经过消息编码器处理后,消息就可以被 Handler 进行处理了

 

2.消息编码过程

Android 端的返回值是 java.lang.Integer 类型的,而 iOS 端返回值则是一个 NSNumber 类型的(通过NSNumber numberWithInt:获取)。而到了 Flutter 端时,这个返回值自动变成Dart 语言的 Int 类型。

standard platform channels 使用 standard messsage codec message response 进行序列化和反序列化,message response 可以是 booleans, numbers, Strings, byte buffers,List, Maps 等等,而序列化后得到的则是二进制格式的数据

Flutter 默认的消息编码器是 StandardMessageCodec ,支持的数据类型如下:3.png

三、MethodChannel

MethodChannel 是 Flutter 与 Platform之间传递信息的一种,其传递过程是:BinaryMessager > BinaryMessagerHandler > MethodChannel。

4.png

如上图:Native 端(iOS 和 Android)为宿主端(host)Flutter 则是客户端(client),Flutter 调用 Native 方法时,需要传递的信息是通过平台通道传递到宿主端的,Native 收到调用的信息后方可执行指定的操作。如有返回的数据,则 Native 会将数据再通过平台通道一并传递给 Flutter,其中数据传递是异步的,这样就能确保消息传递时用户界面不会被阻塞。

 

1.Flutter 层(Dart 层)

Flutter 端使用 MethodChannel 的 invokeMethod 方法发起一次方法调用时,开始了消息传递流程。 invokeMethod 方法会将其入参 message 和 arguments 封装成一个 MethodCall 对象,并使用 MethodCodec 将其编码为二进制格式数据,再通过 BinaryMessages 将消息发出。(注意,此处提到的类名与方法名均为 Dart 层的实现)

 上述过程最终会调用到 ui.Window 的 _sendPlatformMessage 方法,该方法是一个 Native 方法,其实现在 Native 层,这与 Java 的 JNI 技术非常类似。我们向 Native 层发送了三个参数:

            name,String 类型,代表 Channel 名称

            dataByteData 类型,即之前封装的二进制数据

            callback,Function 类型,用于结果回调

2.Native

Native 层后,window.cc 的 SendPlatformMessage 方法接受了来自 Dart 层的三个参数,并对它们做了一定的处理:Dart 层的回调 callback 封装为 Native 层的 PlatformMessageResponseDart 类型的 response;dart 层的二进制数据 data 转化为 std::vector<uint8t> 类型数据 data;根据 response, data 以及 Channel 名称 name 创建一个 PlatformMessage 对象,并通过 dartstate->window()->client()->HandlePlatformMessage 方法处理 PlatformMessage 对象。

dart_state->window()->client() 是一个 WindowClient,而其具体的实现为 RuntimeController,RuntimeController 会将消息交给其代理 RuntimeDelegate 处理。

RuntimeDelegate 的实现为 Engine,Engine 在处理 Message 时,会判断该消息是否是为了获取资源(channel 等于"flutter/assets"),如果是,则走获取资源逻辑,否则调用 Engine::Delegate 的 OnEngineHandlePlatformMessage 方法。

Engine::Delegate 的具体实现为 Shell,其 OnEngineHandlePlatformMessage 接收到消息后,会向 PlatformTaskRunner 添加一个 Task,该 Task 会调用 PlatformView 的 HandlePlatformMessage 方法。值得注意的是,Task 中的代码执行在 Platform Task Runner 中,而之前的代码均执行在 UI Task Runner 中。

四、消息处理

PlatformView 的 HandlePlatformMessage 方法在不同平台有不同的实现,但是其基本原理是相同的

5.png

1.PlatformView

AndroidPlatformViewAndroid 是 Platformview 的子类,也是其在 Android 端的具体实现。当 PlatformViewAndroid 接收到 PlatformMessage 类型的消息时,如果消息中有 response(类型为 PlatformMessageResponseDart),则生成一个自增长的 responseid,并以 responseid 为 key,response 为 value 存入字典 pendingresponses 中。接着,将 channel 和 data 均转化为 Java 可识别的数据,通过 JNI Java 层发起调用,将 response_id、channel 和 data 传递过去。

 Java 层中,被调用的代码为 FlutterNativeView (BinaryMessager 的具体实现)的 handlePlatformMessage ,该方法会根据 channel 找到对应的 BinaryMessageHandler 并将消息传递给它处理。

 BinaryMessageHandler 处理完成后,FlutterNativeView 会通过 JNI 调用 native 的方法,将 responsedata 和 responseid 传递到 native 层。

 Native 层,PlatformViewAndroid 的 InvokePlatformMessageResponseCallback 接收到了respondid 和 responsedata。其先将 responsedata 转化为二进制结果,并根据 responseid,从 pandingresponses 中找到对应的 PlatformMessageResponseDart 对象,调用其 Complete 方法将二进制结果返回。

2.PlatformViewIOS

PlatformViewIOS 是 PlatformView 的子类,也是其在 iOS 端的具体实现,当 PlatformViewIOS 接收到 message 时会交给 PlatformMessageRouter 处理。

PlatformMessageRouter 通过 PlatformMessage 中的 channel 找到对应的 FlutterBinaryMessageHandler,并将二进制消息其处理,消息处理完成后,直接调用 PlatformMessage 对象中的 PlatformMessageResponseDart 对象的 Complete 方法将二进制结果返回

3.结果回传

PlatformMessageResponseDart 的 Complete 方法向 UI Task Runner 添加了一个新的 Task,这个 Task 的作用是将二进制结果从 native 的二进制数据类型转化为 Dart 的二进制数据类型 response,并调用 Dart 的 callback 将 response 传递到 Dart 层。

Dart 层接收到二进制数据后,使用 MethodCodec 将数据解码,并返回给业务层。至此,一次从 Flutter 发起的方法调用就完整结束了

五、具体使用

6.png

1.Flutter 端调用 Android 方法

2.Android 端代码

1)继承 MethodCallHandler 并设置 Handler MethodChannel 需要保存在对象一会调用回调时需要使用,onMethodCall Flutter 层回调的方法这边用 RCIMFlutterWrapper 承接处理

7.png2RCIMFlutterWrapper 类中处理, MethodCall Method,对应 Flutter 层调用 invokeMethod 方法的传入的第一个参数,两端需完全对应一致

8.png

9.png

(3)直接通过 result 对象回调回去这样就能将结果回调

3.关于 Android 回调 Flutter 的使用

10.png

(1)Flutter 端回调监听,设置监听 Key 两端对应

 11.png

(2)Android 端代码回调, mChannel.invokeMethod 方法将数据回调给 Flutter 层



Android 端如何添加自定义表情

IM即时通讯大兴 发表了文章 • 0 个评论 • 567 次浏览 • 2020-12-02 16:28 • 来自相关话题

实现步骤1.新建 RongEmoticonTab 类继承 IEmoticonTab 。public class RongEmoticonTab implements IEmoticonTab {  &... ...查看全部

微信截图_20201202162412.png

实现步骤

1.新建 RongEmoticonTab 类继承 IEmoticonTab 。

public class RongEmoticonTab implements IEmoticonTab {
    public RongEmoticonTab() {
    }
    @Override
    public Drawable obtainTabDrawable(final Context context) {
        return context.getResources().getDrawable(R.drawable.u1f603);
    }
    @Override
    public View obtainTabPager(Context context) {
        return view;
    }
    @Override
    public void onTableSelected(int i) {
    }
}

2.在 obtainTabPager 中添加您想要展示在表情面板上的 view 。

 @Override
    public View obtainTabPager(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.view_emoji, null);
        RecyclerView rv = view.findViewById(R.id.recycler_view);
        //LinearLayoutManager是用来做列表布局,也就是单列的列表
        GridLayoutManager mLayoutManager = new GridLayoutManager(context, 5, OrientationHelper.VERTICAL, false);
        rv.setLayoutManager(mLayoutManager);
        //谷歌提供了一个默认的item删除添加的动画
        rv.setItemAnimator(new DefaultItemAnimator());
        rv.setHasFixedSize(true);
        //模拟列表数据
        ArrayList newsList = new ArrayList<>();
        TypedArray array = context.getResources().obtainTypedArray(context.getResources().getIdentifier("rc_emoji_res", "array", context.getPackageName()));
        int i = -1;
        while (++i < array.length()) {
            newsList.add(array.getResourceId(i, -1));
        }
        rv.setAdapter(new NewsAdapter(newsList));
        return view;
    }

3.Adapter 和布局文件可以参考GitHub
4.自定义一个 ExtensionModule 继承自 DefaultExtensionModule,复写其中的 getEmoticonTabs() 方法,返回需要展示的 EmoticonTab 列表。

public class MyExtensionModule extends DefaultExtensionModule {
    private RongEmoticonTab rongEmoticon;
    @Override
    public List<IEmoticonTab> getEmoticonTabs() {
        List<IEmoticonTab> emoticonTabs =  super.getEmoticonTabs();
        RongEmoticonTab emojiTab=new RongEmoticonTab();
        emoticonTabs.add(myEmoticon);
        return emoticonTabs;
    }
}

5.在初始化之后,取消 SDK 默认的 ExtensionModule,注册自定义的 ExtensionModule, 如下:

public void setMyExtensionModule() {
    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 MyExtensionModule());
        }
    }
}

6.如果需要网络下载表情需要下载并持久化表情数据,需要在添加 EmoticonTab 前下载好表情数据。

融云 IM SDK 集成 —- 刷新会话界面和会话列表界面

IM即时通讯大兴 发表了文章 • 0 个评论 • 678 次浏览 • 2020-12-02 16:28 • 来自相关话题

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行... ...查看全部

最近集成融云 IMkit 发现, 融云 IMkit 提供的 ConversationListFragment 和 ConversationFragment 都没有提供刷新方法.

微信截图_20201202162201.png

我们有个需求是, 使用 Message 的 extra 修改值后, 但是界面没有进行刷新, 需要自己调用代码进行刷新, 但是, 融云没有提供对应的刷新界面的方法, 所以只能看代码.

刷新 ListView 是需要获取 Adapter , 然后使用 Adapter 进行刷新, 但是我们无法从 ConversationListFragment 中直接获取, 那怎么办呢?

我们知道, ListView 中有 getAdapter 的方法. 那我们能不能先获取 ListView 控件呢? 答案是可以的. 我们通过 findById 方法即可获取 ListView. 通过查找, 我们可以知道, 融云会话列表的 listView 的 id 为 R.id.rc_list.

所以代码如下.

ListView mList = (ListView)findViewById(R.id.rc_list);
HeaderViewListAdapter adapter = (HeaderViewListAdapter)mList.getAdapter();
ConversationListAdapter wrappedAdapter = (ConversationListAdapter)adapter.getWrappedAdapter();
if (adapter != null) {
    wrappedAdapter.notifyDataSetChanged();
}


融云即时通讯SDK集成 — 通知检查

IM即时通讯大兴 发表了文章 • 2 个评论 • 658 次浏览 • 2020-12-02 16:28 • 来自相关话题

背景:最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).他家的官网和文档地址:... ...查看全部

微信截图_20201202162632.png背景:

最近公司新上的app要加上即时通讯的功能, 自己快速实现一个当然是不可能的了(项目deadline也顶不住哇).就从各家成熟的SDK厂商选来选去的, 各有各的好也各有各的不足.最后点兵点将,选了融云家的SDK(老板说了算hhhh).
他家的官网和文档地址:
官网:https://www.rongcloud.cn/
文档:https://docs.rongcloud.cn/v4
这个任务当然还是落在我的头上. 集成完毕后, 也踩了不少坑. 所以这篇文章给大家总结下排查融云消息的本地通知和远端推送的办法. 希望可以帮助到正在看这篇文章的你.

什么是本地通知:

当我的App接入了融云的即时通讯sdk后, 便拥有了即时通讯的能力. sdk与融云服务器建立长连接, 当消息发出后, 先走到融云的服务器, 再转发给相应的用户. 这里移动端到服务端, 服务端到移动端, 走的通道都是长连接. 无论你的app是在前台还是在后台, 只要没有被杀死, 那么长连接是一直在的. 所以消息可以即时的发送到达给接收者. 融云把这种走长连接到达的消息, 在通知栏展示的通知叫做本地通知. 也就是消息是顺利发送到接收者端了, 逻辑可以走到消息接收监听那里. 融云sdk内部实现了消息到达后的本地通知, 也赋予了开发者自行实现消息到达后进行本地通知的权利.

本地通知的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

1.是否设置了 setOnReceiveMessageListener 监听, 并且 onReceived 方法返回的为 true。

 RongIM.setOnReceiveMessageListener(new RongIMClient.OnReceiveMessageListener() {
              @Override
              public boolean onReceived(Message message, 
                  return true;
              }
          });

如果 onReceived 方法返回值为 true 则是监听做了拦截, 则不会走通知逻辑。 2.发送的消息是否是自定义消息。 如果是自定义消息, 则请查看自定义消息的 MessageTag 的注解是否设置了 flag 的值为 MessageTag.ISCOUNTED 或 MessageTag.ISPERSISTED) 。 以下面为例。

@MessageTag(value = "RC:TxtMsg", flag = MessageTag.ISCOUNTED)
  @DestructionTag
  public class TextMessage extends MessageContent {
      ...
  }

如果没有设置其中之一,则不会走通知逻辑。

3.是否设置消息拦截器

设置代码如下:

/**
   * 设置接收消息时的拦截器
   *
   * @param messageInterceptor 拦截器
   */
  public void setMessageInterceptor(new RongIM.getInstance().MessageInterceptor() {
          @Override
          public boolean intercept(final Message message) {
                 return true;
          }
 });

如果设置了消息拦截器, 并且在 intercept() 方法中返回 true, 表示拦截此消息。 则不会走通知逻辑。

4.检查接收到的消息的 senderUserId 是否和当前用户的 id 相同。
如果 id 相同, 则不会走通知逻辑。 自己不可发给自己。

5.查看当前接收的消息是否是属于聊天室会话类型的
聊天室的消息目前不支持本地通知。

6.如果是自定义消息,请检查是否设置了自定义消息的 MessageProvider 。
如果没有,则不会走通知逻辑。

7.接收方本地是否有接收方的用户信息。 如果没有设置, 则不会走通知逻辑。

8.检查应用权限设置,看是否打开应用信任权限, 通知栏权限或者声音提示权限等。如果没有打开,请打开再试。

9.接收方没有发送方的用户信息,接收端不弹通知。用户信息通过用户信息提供者方式获取。
参考链接:https://docs.rongcloud.cn/v3/views/im/ui/guide/private/user/set/android.html

什么是远端推送:

集成了即时通讯的SDK, 我们的app不就能拥有像微信一样随时随地收到消息的即时通讯能力了? 说实话我一开始也是这么认为的. 可惜做开发也要按基本法来, Android平台回收app的这一关咱都过不了, app都给你杀死得透透的了你拿啥收消息呢? 咱又不是微信hhhh. 所以一番急赤白脸地阅读他家文档之后, 才发现app如果活着, 他融云能用自己的通道给你把消息推送到. 如果app被杀死了, 这个消息就在他家的服务端直接交给三方厂商了(也就是五大厂商蓝绿大厂华为小米FCM), 让这条消息走人家厂商的推送通道给送到你手机上.

远端推送的检查:

这里我总结了一下接入融云sdk后, 关于本地通知接收不顺利的排查. 大致可以分为这么几条:

  1. 退出应用的时候,只能调用融云的 disconnect() 方法,而不是 logout()。这样退出后融云才会启动push进程。

  2. push 进程的名字不能更改,必须是默认的名字,既 io.rong.push.
    (2.6.0dev之后的版本,此进程名字可以修改)

  3. 通过 ddms 或者终端里敲入 adb shell ps|grep rong 查看终端里是否存在 io.rong.push 这个进程。

有些手机厂家做了特殊限制,不允许第三方后台进程启动,所以融云的后台进程起不来,导致收不到 push 消息。这种情况可以换个手机测试(如三星,大部分三星手机没有做权限限制)。另外大部分国产手机,有权限设置的菜单,比如小米,华为等,可以手动去安全中心,设置应用的自启动权限,后台运行权限等,就可以收到Push消息了。

不过 vivo 和 oppo 有些型号的手机,一旦应用退到后台,系统会很快把它杀死,这种没有办法解决。目前市面上所有推送都存在这个问题,除非系统把该应用加入白名单。

  1. push 进程存在,仍然收不到 push 消息。

如果你的应用有消息免打扰功能,那么请确认当前登录账号之前是否设置过消息免打扰。如果不太确定,那最好去你的应用设定里重新设置下消息免打扰时间。 这里要注意的是: 如果这个账号之前在别的手机上设置过消息免打扰,换一台手机登录或者卸载重装的时候,融云服务端记录的仍然是之前设置的消息免打扰状态,所以这种情况下是收不到push消息的。


解决融云 SDK 4.0 版本配置 https 导航报 SSLHandshakeException

IM即时通讯大兴 发表了文章 • 0 个评论 • 575 次浏览 • 2020-12-02 16:28 • 来自相关话题

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.之前我们是使用的2.x 版本, 使用下面接口配置一下即可.    RongIMClient.getInsta... ...查看全部

微信截图_20201202162800.png

我们公司最近使用融云 IM 进行集成开发. 我们是私有云部署, 导航是通过接口进行设置的. 是 Https 的.

之前我们是使用的2.x 版本, 使用下面接口配置一下即可.

    RongIMClient.getInstance().enableHttpsSelfCertificate(false);

但是最近我们升级融云到 4.0 版本. 突然发现连接不上了. 日志中报

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

这个错误.

于是咨询融云技术支持, 得知 4.0 需要自己进行证书验证.

需在 application 中添加即可.

 try {
    //使用X509TrustManager代替CertificateTrustManager跳过证书验证。
    TrustManager tm[] =  new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };
        SSLContext context = SSLContext.getInstance("TLS");   
        context.init(null, tm, null);
        SSLUtils.setSSLContext(context);
        SSLUtils.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
             }
            });
} catch (Throwable e) {
    throw new IllegalStateException(e);
}

注意的是, 由于导航请求是在融云的 ipc 进程中的, 所以在 application 中设置上面代码的时候, 千万不要设置在主进程中. 否则无效.

融云清空历史消息 Android 端

IM即时通讯大神庵 发表了文章 • 0 个评论 • 598 次浏览 • 2020-12-02 16:16 • 来自相关话题

融云清空历史消息 Android 端先调用获取历史消息。/** * 根据会话类型的目标 Id,回调方式获取N条历史消息记录。 * * @param conversationType 会话类型。不支持传入&nbs... ...查看全部

融云清空历史消息 Android 端

微信截图_20201202161404.png

  1. 先调用获取历史消息。

/**
* 根据会话类型的目标 Id,回调方式获取N条历史消息记录。
*
* @param conversationType 会话类型。不支持传入 ConversationType.CHATROOM。
* @param targetId         目标 Id。根据不同的 conversationType,可能是用户 Id、讨论组 Id、群组 Id。
* @param oldestMessageId  最后一条消息的 Id,获取此消息之前的 count 条消息,没有消息第一次调用应设置为:-1。
* @param count            要获取的消息数量。
* @param callback         获取历史消息记录的回调,按照时间顺序从新到旧排列。
*/
public void getHistoryMessages(Conversation.ConversationType conversationType, String targetId, int oldestMessageId, int count, RongIMClient.ResultCallback<List<Message>> callback) {
RongIMClient.getInstance().getHistoryMessages(conversationType, targetId, oldestMessageId, count, callback);
}
  1. 再调用删除的接口。

/**
* 删除指定的一条或者一组消息,回调方式获取是否删除成功。
*
* @param messageIds 要删除的消息 Id 数组。
* @param callback   是否删除成功的回调。
*/
public void deleteMessages(final int[] messageIds, final RongIMClient.ResultCallback<Boolean> callback) {
RongIMClient.getInstance().deleteMessages(messageIds, new RongIMClient.ResultCallback<Boolean>() {
@Override
public void onSuccess(Boolean bool) {
if (bool)
RongContext.getInstance().getEventBus().post(new Event.MessageDeleteEvent(messageIds));
if (callback != null)
callback.onSuccess(bool);
}
@Override
public void onError(RongIMClient.ErrorCode e) {
if (callback != null)
callback.onError(e);
}
});
}

清除远端消息调接口

  1. cleanRemote 传 true ,时间戳传入当前的时间戳。

/**

  • 删除指定时间戳之前的消息,可选择是否同时删除服务器端消息

  • 此方法从服务器端清除历史消息,但是必须先开通历史消息云存储功能。

  • 根据会话类型和 TargetId 清除某一会话指定时间戳之前的本地数据库消息(服务端历史消息),

  • 清除成功后只能从本地数据库(服务端)获取到该时间戳之后的历史消息。

*
* @param conversationType 会话类型。
* @param targetId         会话目标ID。
* @param recordTime       清除消息截止时间戳,【0 ~ 当前时间的 Unix 时间戳】。
* @param cleanRemote      是否删除服务器端消息
* @param callback         清除消息的回调。
*/
public void cleanHistoryMessages
  1. 如果界面没有刷新,重新进入看下是否全部删除了。

您先试下是否可以删除,如果可以删除,但是界面没有刷新刷新的话,您可以调用 ConversationFragment 的

getMessageAdapter
方法,然后

mListAdapter.removeAll();

mListAdapter.notifyDataSetChanged();
试下。