技术

技术

关于密码你不知道的冷知识?至今还有 3% 的人使用“123456”作为密码

科技创新fanta2 发表了文章 • 0 个评论 • 79 次浏览 • 2021-02-04 10:11 • 来自相关话题

之前我们报导过 2020年被用烂大街的密码《2020 最烂密码 TOP 200 曝光!》,500 多万个泄漏密码表明,共有近 3% 的人使用“123456”作为密码。而最近知名黑客网站 Have I Been Pwned 上一个密码“ji32k7au4a83”... ...查看全部

之前我们报导过 2020年被用烂大街的密码《2020 最烂密码 TOP 200 曝光!》,500 多万个泄漏密码表明,共有近 3% 的人使用“123456”作为密码。而最近知名黑客网站 Have I Been Pwned 上一个密码“ji32k7au4a83”的使用次数引起了热烈讨论。

Have I Been Pwned 是一个可以查询用户的邮箱是否被泄漏的网站,它的一个密码查询功能 Pwned Passwords 记录着在数据泄露中暴露的 551 509 767 个真实密码,用户可以在这里查询某个密码被使用的次数。比如查询一下 2018 年最烂密码“123456”,得到 23 174 662 次的结果:

1.jpg

“123456”这样简单易记的数字串被很多人作为密码使用,这很容易理解,但是有一个密码 “ji32k7au4a83”的使用次数让人费解,并且在最近引起了热烈讨论。

硬件/软件工程师 Robert Ou 最开始在 Pwned Passwords 上发现“ji32k7au4a83”竟然有 141 次使用,他觉得这不太正常,因为这个字符串太“偏”了,于是他在社区提问。

随后就有人指出了原因(估计就是使用者),原来“ji32k7au4a83”是汉语注音符号系统中“我的密码”的对应字符串。

汉语注音符号是以章太炎的记音字母作蓝本,1913 年由中国读音统一会制定,1918 年北洋政府教育部正式颁行的一套注音系统。

2.png

1918 年第一个版本如下:

声母介音韵母
ㄍㄎㄫㄐㄑㄬㄉㄊㄋㄅㄆㄇㄈㄪㄗㄘㄙㄓㄔㄕㄏㄒㄌㄖㄧㄨㄩㄚㄛㄝㄟㄞㄠㄡㄢㄤㄣㄥㄦ

经过一个多世纪的发展,目前注音符号已经变化不少。目前中国台湾、澎湖、金门与马祖仍在使用注音符号,详情可以查看百科。

3.jpg

以上中招的朋友看完本文请记得修改密码。

据说,黑客会根据网民习惯设置的密码,收集起来编写成为“常用密码库”,直接使用现成的常用密码库去匹配破解,往往就能短时间破解大量账号。有安全机构研究显示,黑客大约只要17分钟就可以破解1000个帐号。

那我们平时该如何去设置密码呢?

安全专家的建议是,设置密码满足这三点:

  1. 密码长度最好8位或以上;

  2. 密码没有明显的组成规律;

  3. 尽量使用三种以上符号,如“字母+数字+特殊符号”。


安卓使用官方的demo好友问题

IM即时通讯admin 回复了问题 • 2 人关注 • 1 个回复 • 113 次浏览 • 2021-01-21 18:17 • 来自相关话题

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

IM即时通讯dht1212 发表了文章 • 0 个评论 • 218 次浏览 • 2021-01-08 11:13 • 来自相关话题

升级 Xcode12 后,模拟器都是 iOS14 了,运行自己的项目,到了聊天页面就崩溃,具体崩溃信息为Thread 1: “-[_UIPageControlIndicatorContentView setImage:]: unrecognized selec... ...查看全部

升级 Xcode12 后,模拟器都是 iOS14 了,运行自己的项目,到了聊天页面就崩溃,具体崩溃信息为Thread 1: “-[_UIPageControlIndicatorContentView setImage:]: unrecognized selector sent to instance。

从崩溃看,猜测是因为缺少了某个方法导致了崩溃,提了个工单给融云,他们的技术支持服务还是挺到位的,很快给了答复,他们对这个问题已经做了及时处理(其实他们已经发了站内信和邮件,自己没注意),更新了官网下载和 pod 上的 SDK,卸载现有 SDK,重新下载 2.10.4 以上版本 SDK 就可以了。

并且这个崩溃只针对使用 imkit的,使用 imlib 的用户没事儿,下面是他们在工单里面回复的具体内容:

通过 Xcode 12 打包 App,在 iOS 14 版本中我司发现 2.x 、4.0.0 、4.0.0.1 版本的 IMKit SDK 与 iOS 14 出现了兼容问题,该问题会引起 App 崩溃,针对该问题进行了紧急的修复
您可以在如下版本获得修改后的 SDK 版本:
1、iOS IMKit SDK 版本为 4.0.0 和 4.0.0.1 的客户务必升级至 4.0.1+ 版本。
2、iOS IMKit SDK 版本为 2.x 客户务必升级至 2.10.6-DEV 或者 2.10.4-Stable( 9 月 18 日发布的版本),这两个版本已针对该问题进行了修复
(1)请务必于 2020 年 9 月 18 日以后重新下载获取 SDK,更新您的 App,下载地址:https://www.rongcloud.cn/downloads/history/ios
(2)使用 Pod 集成的客户,请参考文档先清理 Pod 缓存,文档地址:https://docs.rongcloud.cn/v4/views/im/ui/guide/private/setting/include/ios.html#cocoapods-clean
3、基于 iOS IMLib SDK 集成的客户不受影响。


小吐槽一下,这个崩溃问题虽然发了站内信和邮件,但还是不太容易一起注意,如果能在下面的官网或文档里面有个弹窗或浮动提示就更好了

融云官网:https://www.rongcloud.cn/
融云文档:https://docs.rongcloud.cn/v4


简单介绍融云 imkit 包含功能

IM即时通讯dht1212 发表了文章 • 0 个评论 • 220 次浏览 • 2021-01-08 11:13 • 来自相关话题

说明本篇文章简单介绍一下融云 imkit 包含的功能,大家可在阅读之后来对大体内容有一个基础的了解。详细内容还请翻阅 官方文档基本内容融云 imkit 是为了方便开发者快速集成而开发的一套 UI 库,里面主要包含三部分内容:用户信息会话列表会话页面用... ...查看全部

说明

本篇文章简单介绍一下融云 imkit 包含的功能,大家可在阅读之后来对大体内容有一个基础的了解。详细内容还请翻阅 官方文档

基本内容

融云 imkit 是为了方便开发者快速集成而开发的一套 UI 库,里面主要包含三部分内容:

  • 用户信息

  • 会话列表

  • 会话页面

image.png

用户信息

image.png

用户信息是指融云 SDK 提供了一套完整的用户信息的显示与存储机制,里面包含了用户信息、群信息、群名片信息,开发者仅仅需要将自己 App Server 内的用户信息包装成一个融云的特有的用户信息对象,然后传递给对应的接口即可。之后融云 SDK 会自己帮助显示对应用户的用户信息,并存储在本地数据库,方便后续的读取。

大体的显示流程可参考官方的流程图:

image.png

会话列表

image.png

会话列表是融云 SDK 根据消息生成的一个 list

当收到一条新消息并且会话列表没有当前会话的时候,SDK 会自动生成一条新的会话数据,并添加到 tableview 中。

需要注意的是融云的会话列表不会存储在服务器中,只在本地存储。当切换设备时,需要去融云开发者后台开通多设备同步,这样在新设备登录的时候,会触发融云的消息补偿,当移动端接收到消息的时候,会在新设备也生成一个新的会话列表。正常情况就是你设置几天就补偿几天。

支持的功能有:

  • 会话置顶

  • 会话删除

  • 会话免打扰

  • 已读回执显示

  • 有人@显示

会话页面

image.png

融云的会话页面整体可分为两部分

  • 消息展示区

  • 输入框

消息展示

消息展示就是当前用户收发消息展示的地方,和常规 APP 一样,接收在左边,发送在右边。发送方是不显示昵称的,接收方可根据配置来选择是否显示昵称。

SDK 自带的消息展示有

  • 文本消息

  • 语音消息

  • 图片消息

  • GIF 消息

  • 视频消息

  • 位置消息

  • 文件消息

  • 小灰条消息

开发者还能根据自己的需求来自定义其他消息,自定义消息有两种用法:

  1. 发送其他需要展示的消息,对应绑定一套 UI 组件,收到消息后,融云SDK 帮助你把绑定的这套 UI 展示到界面上。

  2. 当做控制消息:控制消息就是你不展示到界面上,但是你可以利用消息机制来做处理,从服务器或者其他地方下发一个指令,收到这个消息后,UI不会发生任何变化。但你可以根据这条消息来处理你的业务操作。比如刷新某个页面,获取某个信息等等

开发者可以继承融云的会话页面,在其子类来进行其他操作,在进入会话页面的时候,SDK 会自动拉取当前会话的历史聊天记录,这个操作会先从本地数据库获取,如果不够 10 条的话,会从服务器获取,需要开通历史消息云存储功能。(融云 SDK 会在本地搭建一套数据库,用来存储你所有的聊天内容)

在获取到历史记录之后,SDK 会自动帮你展示到 消息展示区。展示出来的消息都支持以下功能:

  • 发送出去的消息支持已读回执(单群聊)

  • 消息撤回:kit 默认为两分钟

  • 消息多选

  • 消息转发:支持合并转发

  • 消息引用

  • 消息删除

输入框

SDK 提供的输入框共分为四部分:

  • 文本输入

  • 语音输入

  • 表情

  • 扩展板

文本输入:

文本输入支持用户输入任何文本

群聊输入 @ 可触发@ 人功能

语音输入:

语音输入分为普通语音消息高清语音消息

高清语音消息是在2.9.25之后支持的。建议使用此套方案。

表情:
SDK 有一套默认的 emoji 表情,且支持表情自定义。

扩展板:

扩展板也就是 + 号区域,SDK 默认支持的功能有:

  • 音视频(需要集成 融云音视频,集成之后会自动出现)

  • 照片

  • 位置:支持位置实时共享

  • 语音输入:需要使用科大讯飞的库来做,融云提供了相关的库内容

  • 名片

  • 文件:文件消息是 SDK 下载到指定目录的文件,并非手机系统的文件。

  • 其他:自定义内容,可根据自己的业务添加其他的小内容。

此外,SDK还提供了 常用语功能,目前仅支持单聊,且字数在 30 字以内,可进行类似快捷回复之类的操作。

其他内容,后续补充。

更多详细的内容可自己阅读官网文档。


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

IM即时通讯赵炳东 发表了文章 • 0 个评论 • 231 次浏览 • 2021-01-05 10:55 • 来自相关话题

1、必备资料 github AMR 开源库 :https://github.com/jpemartins/amr.js 用心把这个项目看一遍,对于我下面说的话,可以忽略啦,代码是最好的文章,哈哈~~2、核心 JS 库 :amr.js 、pcmdata... ...查看全部

1、必备资料 github AMR 开源库 :https://github.com/jpemartins/amr.js 用心把这个项目看一遍,对于我下面说的话,可以忽略啦,代码是最好的文章,哈哈~~

2、核心 JS 库 :amr.js 、pcmdata.min.js、libamr-nb.js (g上述ithub项目中有另外三个js,我给合成一个amr.js,不要混乱)这三个 JS 是播放声音的主要依赖,下面一 一 介绍下:

① amr.js : 可以理解成 “桥” 的概念,连接底层解码和客户端调用的桥梁。

② pcmdata.min.js : 封装 pcm data, PCM是一种编码格式 , PCM 添加上 RIFF 头 可以组成 Wave 音频(Wave 不止这一种编码方式),细节太深,小弟也不懂啦~~,有空深挖。

③ libamr-nb.js : amr转换的核心库,这个文件比较大,压缩后大概也要 600kb 左右。

④ 如果下载三个核心库,建议从上篇 “Web 播放声音 — 介绍篇” 中的附件中卸载,在 github 现在的核心库,在 Chrome 下只能播放一次,把这个问题修复了一下,原因是以同样的 Audio.src 创建 Audio 在 Chrome 下就不能二次播放,网上说法各异,最终自己动手解决了,详细可以在 amr.js 中的 global.util.play 方法中看。

3、具体用法

说了有一阵子白话了,该上点代码了,下面是我封装的播放 AMR 格式 Base64 码 的插件,直接在页面引用可以 ,下载地址可以在 “Web 播放声音 — 介绍篇” 下载。

页面一点样式都没有加,只有两个按钮,小伙伴凑合凑合,哈哈~~

代码中只引入了 voice.js 一个 js,其他 js 动态引用了。(公司有 cdn 所以直接放在上面了)。

步骤:

第一步:执行初始化。

第二步:调用相应方法。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Example</title>
<script src="js/voice.js"></script>
</head>
<body>
<input type="button" value="play" id="playId"/>
<input type="button" value="stop" id="stopId"/>
</body>
<script type="text/javascript">
//初始化
RongIMLib.RongIMVoice.init();
//播放
document.getElementById("playId").onclick = function(){
   // base64Str : amr 格式的 base64 码,可以用 HTML5 FileReader 或者canvas 进行转换 。 
   var base64Str = "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";
    RongIMLib.RongIMVoice.play(base64Str );
};
//暂停
document.getElementById("stopId").onclick = function(){ RongIMLib.RongIMVoice.stop(); }; 
</script> 
</html>

在这里也把 “voice.js” 的代码贴出来,使用 typescript 写完后生成出来的,方便小伙伴们查看:

var RongIMLib;
(function (RongIMLib) {
 var RongIMVoice = (function () {
  function RongIMVoice() {
  }
  /**
  * 初始化声音库
  */
  RongIMVoice.init = function () {
      if (this.isIE) {
          var div = document.createElement("div");
          div.setAttribute("id", "flashContent");
          document.body.appendChild(div);
          var script = document.createElement("script");
          script.src = "http://cdn.ronghub.com/swfobject-2.0.0.min.js";
          var header = document.getElementsByTagName("head")[0];
          header.appendChild(script);
          setTimeout(function () {
              var swfVersionStr = "11.4.0";
              var flashvars = {};
              var params = {};
              params.quality = "high";
              params.bgcolor = "#ffffff";
              params.allowscriptaccess = "always";
              params.allowfullscreen = "true";
              var attributes = {};
              attributes.id = "player";
              attributes.name = "player";
              attributes.align = "middle";
              swfobject.embedSWF("http://cdn.ronghub.com/player-2.0.2.swf", "flashContent", "1", "1", swfVersionStr, null, flashvars, params, attributes);
          }, 200);
      }
      else {
          var list = ["http://cdn.ronghub.com/pcmdata-2.0.0.min.js", "http://cdn.ronghub.com/libamr-2.0.1.min.js"];
          for (var i = 0, len = list.length; i < len; i++) {
              var script = document.createElement("script");
              script.src = list[i];
              document.head.appendChild(script);
          }
      }
      this.isInit = true;
  };
  /**
  * 开始播放声音
  * @param data {string} amr 格式的 base64 码
  * @param duration {number} 播放大概时长 用 data.length / 1024
  */
  RongIMVoice.play = function (data, duration) {
      this.checkInit("play");
      var me = this;
      if (me.isIE) {
          me.thisMovie().doAction("init", data);
      }
      else {
          me.palyVoice(data);
          me.onCompleted(duration);
      }
  };
  /**
  * 停止播放声音
  */
  RongIMVoice.stop = function () {
      this.checkInit("stop");
      var me = this;
      if (me.isIE) {
          me.thisMovie().doAction("stop");
      }
      else {
          if (me.element) {
              me.element.stop();
          }
      }
  };
  /**
  * 播放声音时调用的方法
  */
  RongIMVoice.onprogress = function () {
      this.checkInit("onprogress");
  };
    /** 
     * 校验是否初始化
      */
  RongIMVoice.checkInit = function (postion) {
      if (!this.isInit) {
          throw new Error("RongIMVoice not initialized,postion:" + postion);
      }
  };
  RongIMVoice.thisMovie = function () {
      return eval("window['player']");
  };
  RongIMVoice.onCompleted = function (duration) {
      var me = this;
      var count = 0;
      var timer = setInterval(function () {
          count++;
          me.onprogress();
          if (count >= duration) {
              clearInterval(timer);
          }
      }, 1000);
      if (me.isIE) {
          me.thisMovie().doAction("play");
      }
  };
  RongIMVoice.base64ToBlob = function (base64Data, type) {
      var mimeType;
      if (type) {
          mimeType = { type: type };
      }
      base64Data = base64Data.replace(/^(.*)[,]/, '');
      var sliceSize = 1024;
      var byteCharacters = atob(base64Data);
      var bytesLength = byteCharacters.length;
      var slicesCount = Math.ceil(bytesLength / sliceSize);
      var byteArrays = new Array(slicesCount);
      for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
          var begin = sliceIndex * sliceSize;
          var end = Math.min(begin + sliceSize, bytesLength);
          var bytes = new Array(end - begin);
          for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
              bytes[i] = byteCharacters[offset].charCodeAt(0);
          }
          byteArrays[sliceIndex] = new Uint8Array(bytes);
      }
      return new Blob(byteArrays, mimeType);
  };
  RongIMVoice.palyVoice = function (base64Data) {
      var reader = new FileReader(), blob = this.base64ToBlob(base64Data, "audio/amr"), me = this;
      reader.onload = function () {
          var samples = new AMR({
              benchmark: true
          }).decode(reader.result);
          me.element = AMR.util.play(samples);
      };
      reader.readAsBinaryString(blob);
  };
  RongIMVoice.isIE = /Trident/.test(navigator.userAgent);
  RongIMVoice.isInit = false;
  return RongIMVoice;
})();
RongIMLib.RongIMVoice = RongIMVoice;
//兼容AMD CMD
if ("function" === typeof require && "object" === typeof module && module && module.id && "object" === typeof exports && exports) {
  module.exports = RongIMVoice;
}
else if ("function" === typeof define && define.amd) {
  define("RongIMVoice", [], function () {
      return RongIMVoice;
  });
}
})(RongIMLib || (RongIMLib = {}));

好啦,到此本文结束,会尽快更新 Flash 下播放 AMR 格式 Base64 码,代码中哪里有错误或者有异议麻烦小伙伴们联系我,一起讨论,有则改之无法加冕,一起进步,thx~~。

以上是融云 SDK 里的源码,不过最近推出了直接使用 Audio 方式播放 AAC 格式的音频文件,非常方便

原始文章连接:https://www.cnblogs.com/yuhongda0315/p/5224188.html

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

融云文档:https://docs.rongcloud.cn/v4


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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 360 次浏览 • 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即时通讯大神庵 发表了文章 • 0 个评论 • 387 次浏览 • 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 即可.


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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 403 次浏览 • 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) {
        }
    });

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

融云清空历史消息 Android 端

IM即时通讯大神庵 发表了文章 • 0 个评论 • 400 次浏览 • 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();
试下。


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

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 366 次浏览 • 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);
    }
}

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

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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 387 次浏览 • 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 个评论 • 360 次浏览 • 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 个评论 • 400 次浏览 • 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 个评论 • 403 次浏览 • 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 个评论 • 366 次浏览 • 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 个评论 • 155 次浏览 • 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 个评论 • 390 次浏览 • 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()方法.


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

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

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

背景:

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

微信截图_20201202155726.png

如何兼容Android Q

Android 10 适配
前言
为了让用户更好地控制自己的文件,并限制文件混乱的情况,Android 10(Q) 修改了 APP 访问外部存储中文件的方法。外部存储的新特性被称为 Scoped Storage。

Android 10(Q) 仍然使用 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 作为面向用户的存储相关运行时权限,但现在即使获取了这些权限,访问外部存储也受到了限制。APP 需要这些运行时权限的情景发生了变化,且各种情况下外部存储对 APP 的可见性也发生了变化 在 Scoped Storage 新特性中,外部存储空间被分为两部分:

● 公共目录:Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones 等

公共目录下的文件在 APP 卸载后,不会删除。

APP 可以通过 SAF(System Access Framework)、MediaStore 接口访问其中的文件。

● App-specific 目录

APP 卸载后,数据会清除。

APP 的私密目录,APP 访问自己的 App-specific 目录时无需任何权限。

融云关于 Android 10(Q)适配
之前 SDK 将媒体文件存储于 /sdcard/RongCloud/Media 中,所以在 android 10(Q) 系统上会有聊天无法使用的情况,新版本更新后,会将媒体消息存储于 :sdcard/Android/data/包名/files/ 中,以保证 sdk 功能的正常使用

用户需要做的适配工作
1.如果您是 kit 的用户,我们对融云基本消息类型进行了全面兼容,如果您自定义了媒体消息,建议您在点击自定义媒体消息时,对本地文件进行检查,如果本地文件流无法读取,调用 RongIM 的 downloadMediaMessage() 方法重新下载更新本地路径

示例代码

//1.判断小视频本地文件是否存在
if (sightMessage.getLocalPath() != null && !TextUtils.isEmpty(sightMessage.getLocalPath().toString())) {
            return FileUtils.isFileExistsWithUri(this, sightMessage.getLocalPath());
        } else {
            return false;
        }
//2.调用 downloadMediaMessage 下载文件刷新 ui
  RongIM.getInstance().downloadMediaMessage(mMessage, downloadMediaMessageCallback);

2.如果您是 lib 的用户,建议您在用户使用到媒体类型消息时,对消息体中的 localPath 进行判断检查,如果本地文件流无法正常访问,请调用 RongIMClient 的 downloadMediaMessage() 方法对媒体文件进行重新下载并更新本地路径,具体代买可参考以上代码进行具体调整

备注
文件存储路径变化

不开启 rc_q_storage_mode_enable 的各种文件保存路径
//使用sight模块录制小视频时
录制视频:sdcard/sdcard/RongCloud/Image/应用名/image/
下载音频 /sdcard/RongCloud/Media
下载视频 /sdcard/RongCloud/Media
下载文件 /sdcard/RongCloud/Media
下载 gif /sdcard/RongCloud/Media
自定义媒体文件 /sdcard/RongCloud/Media
开启 rc_q_storage_mode_enable 的各种文件保存路径
//使用sight模块录制小视频时
录制视频:sdcard/Android/data/包名/files/RongCloud/video/
下载音频:sdcard/Android/data/包名/files/RongCloud/audio/
下载视频:sdcard/Android/data/包名/files/RongCloud/video/
下载文件:sdcard/Android/data/包名/files/RongCloud/file/
下载 gif:sdcard/Android/data/包名/files/RongCloud/image/
自定义媒体文件 会根据媒体类型存储到以上目录中


万维网的诞生 | 当我们在浏览器输入网址并回车后,发生了什么?

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 239 次浏览 • 2020-06-30 17:08 • 来自相关话题

编者按:1989年3月12日,欧洲粒子物理研究所软件顾问蒂姆&middot;李提交一个构建信息管理系统的计划&ldquo;Information Management: A Proposal&rdquo;,以便更好地管理实验室研究信息。... ...查看全部

编者按:1989年3月12日,欧洲粒子物理研究所软件顾问蒂姆&middot;李提交一个构建信息管理系统的计划&ldquo;Information Management: A Proposal&rdquo;,以便更好地管理实验室研究信息。该计划促使了万维网(WWW)诞生。1994年,中科院高能所架设了中国第一台WWW服务器,并推出第一个网站www.ihep.ac.cn,这项工程的亲历者许榕生研究员讲述了万维网(WWW)诞生的历史。

风靡世界的互联网环球信息技术World Wide Web(简称WWW)的发明源自上世纪八十年代。英国人蒂姆&middot;伯纳斯&middot;李(Tim Berners-Lee)于1989年成功地开发出世界上第一个Web服务器和第一个Web客户端软件,把互联网的应用推上了一个崭新的台阶,极大地促进了人类社会的信息化进程。因&ldquo;发明万维网、第一个浏览器和使万维网得以扩展的基本协议和算法&rdquo;而授予了蒂姆&middot;伯纳斯&middot;李图灵奖(2016年)。

互联网的雏形早在1960年代就诞生了,为什么没有迅速流传开来呢?其实,很重要的原因是早年联接到网络上需要经过一系列复杂的操作,并且不同的计算机具有不同的操作系统和不同的文件结构格式,使得跨平台的信息文件只能相互独立地划成孤岛。蒂姆曾经用一副非常形象的图画表明了他的创意(图1),即通过一种超文本方式,把分布在网络上的不同计算机内的信息有机地结合在一起,通过超文本传输协议(HTTP)从任意的Web服务器转到一台Web浏览器上进行无障碍的信息检索。这个叫Web的软件还能支持图文并茂的信息,甚至还允许发布音频和视频。这就使得后来的互联网远程教育及在线购物等等得以实现!此外,互联网的许多其它功能,如E-mail、Telnet、FTP、WAIS等内容也都可通过Web框架进行实现。

1990年12月25日,蒂姆和法国网络高手罗伯特&middot;卡里奥(Robert Cailliau)在西欧高能物理中心(CERN)一起成功地通过互联网展现了基于Web原理的HTTP代理与服务器的第一次通讯。短短的时间内,这项技术推广到了全世界。


图1 蒂姆&middot;伯纳斯&middot;李用这张图说明了WWW的创意,使原来不同计算机上的信息无法沟通,而现在可以用任何一台计算机对任何Web服务器上的信息库进行调用。


英国女皇伊丽莎白二世2004年向伯纳斯&middot;李颁发大英帝国爵级司令勋章。2009年4月,他获选为美国国家科学院外籍院士。2012年夏季奥林匹克运动会开幕典礼上,蒂姆获得了&ldquo;万维网发明者&rdquo;的美誉,他本人也参与了开幕典礼。

根据有关资料介绍,蒂姆&middot;伯纳斯&middot;李出生于英格兰伦敦西南部,他的父母都参加过世界上第一台商业计算机的建造。1973年,他中学毕业进入牛津大学王后学院深造,1976年从牛津大学物理系毕业后曾经供职于一些高科技公司,从事集成电路和系统设计的研究。1980年,一个偶然的机会,蒂姆来到瑞士的日内瓦,进入到CERN的一个实验室组里。该实验室组的首席是华裔物理学家、诺贝尔奖获得者丁肇中教授。

享誉世界的实验物理学家丁肇中教授在基本粒子研究方面取得一系列重大突破,独立发现了第四种夸克的束缚态,即J粒子,由此开拓了基本粒子研究的新领域。那段时期,丁教授在欧洲高能物理研究中心领导着L3的实验,该实验组首次邀请了由美国、前苏联、中国、欧洲等600名科学家共同参加大型国际合作研究。

在那里,年轻的蒂姆接受了一项极富挑战性的工作:为了使实验组里各国的高能物理学家能通过计算机网络及时沟通并传递信息,实验组委托他开发一个软件,以便让分布在各国实验组成员能够把最新的信息、数据、设计图资料等及时地提供给全体人员共享,随时随地犹如大家都在一起地方同步工作。早在牛津大学主修物理时蒂姆就不断地思索,是否可以找到一个"点",就好比人脑,能够透过神经传递、自主作出反应。以此为思路,蒂姆经过一段努力,终于编制成功了第一个高效局部存取浏览器"Enquire",并把它应用于数据共享浏览中。

1984年蒂姆作为正式成员重返欧洲核子物理实验室,他恢复了过去的工作,并正式写下了世界上第一个网页浏览器(World Wide Web)和第一个网页服务器(httpd)的软件源码。这时蒂姆把目标瞄向了建立一个全球范围的信息检索系统,以彻底打破信息存取的孤立行为。1989年3 月,蒂姆向CERN递交了一份立项建议书,建议采用超文本技术(Hypertext)首先把CERN内部的各个实验室连接起来,在系统建成后,可以扩展到全世界。这个激动人心的建议在CERN引起轩然大波,但开始没有被上司通过。

蒂姆并没有灰心,关键是他看到了突破口,是金子总会闪亮的!他花了2个月重新修改了建议书的措辞,最后终于得到了批准。于是蒂姆有了一笔经费,购买了一台NEXT计算机,并率领一批助手在上面开发系统。在1991年8月6日,蒂姆建立了第一个WWW网站(也是世界上第一个网站),网址是http://info.cern.ch/,在这个网站里还罗列出了各国跟进的WWW网站名单。这项利用互联网+超链接的闪亮原创就在CERN顺理成章地迅速推广开来。


蒂姆&middot;伯纳斯&middot;李

时值90年代初,中国建立了北京正负电子对撞机工程,为推动我国高能物理网络环境的进一步发展,我到了位于日内瓦的CERN考察,在访问期间就被安排在蒂姆原来的办公室内,很幸运地接触到这位WWW发明人。蒂姆在办公室门口贴了一张大黑蜘蛛趴在网上的图画,他给我解释说英文里Web也是网。他热情推荐我参加当年(1994年)4月10日召开的第一届国际WWW技术论坛大会,他做主题演讲,我亲眼看到会上会下群情振奋,所有人都感觉到一个重大的事件即将在世界上发生。那天他在屏幕上专门打出一张世界地图,用颜色标明已经推广了WWW技术的国家(当时主要集中在欧美各国),然后他刻意指着中国区域预言这里也快了。因为他知道我已经在组织策划国内着手启用WWW这项技术。

几天后,位于北京的高能物理所架设了中国第一台WWW服务器,推出了第一个网站www.ihep.ac.cn和英文网页,此刻在亚洲还没几个Web网站出现。

那年我也曾经邀请蒂姆到中国做报告,但他当时在世界各国的报告都已经安排满满的了,便派了他的助手到北京介绍WWW。差不多二十年后的2013年蒂姆才第一次得以访问了中国。

今天作为Web之父的蒂姆已经功成名就。但并不像大多数普通人都认为的那样,WWW的建立是通向致富的捷径。与那些依托互联网一夜暴富之士相比,蒂姆仍然坚守在学术研究岗位上,那种视富贵如浮云的胸襟,真正表现了一个献身科学的学者风度。回顾过去,我们看到伟大的全球互联网事业,正是由无数像蒂姆&middot;伯纳斯&middot;李这样的先驱们的无私耕耘才成长起来的。

在WWW诞生29周年之际,63岁的蒂姆发表了一封公开信,信中强调了目前互联网发展似乎正面临着一些威胁。蒂姆意识到了网络的无穷威力将给政府、企业和社会带来的剧烈改变。他预见这个发明一旦落入不适当的人手中,将成为世界的灾难。有人忙着打造一个接一个的社群网络、在线商业平台,很少会去设想他们可能带来什么后遗症。而蒂姆过去近三十年的时间里却都在努力保护互联网的纯洁,使自己的这项发明更好地造福人类,尽管他从未曾因为这个发明而直接获利。

互联网的安全问题日益显示出许多弱点,尤其过去几年里,脸书外泄超过了8000万用户个人资料,更多的网络运营商在想方设法收集个人隐私。

今年初,蒂姆出席了美国华盛顿的全球信息网基金会(World Wide Web Foundation)年度会议,这个基金会致力于保障数字环境中的人权。他认为这是一项迫切的任务,因为据他的估计,到今年下半年全球网络人口将达到四十亿。随着越来越多人在网络上分享他们的履历、政治倾向、DNA信息,网络将变得更加强大、更有价值,但同时也更加危险。

一开始,WWW技术确实是开放、自由、不受任何公司或团体所控制。不过,这种免费与开放的精神,却也成了它的弱点。如今我们几乎是自愿地让某些网络运营商控制着我们要去哪里买东西、要看到什么新闻、要对什么人点赞。

在早年网络泡沫时期,有人评论在中国WWW网站的涌现率比人口出生率还高。实际上,我们有世界上最多的用户,但是也有很多的问题。涉及到网络安全,不能不说我们用户和运营商的安全意识还需努力提高。网络安全形势不可小视,蒂姆给我们表示的忧虑不是空穴来风,它预示着互联网新的一波挑战又在开始。不管是加固WWW的系统,还是采用所谓&ldquo;去中心化&rdquo;的架构,我们都拭目以待。

来源:中国科学院高能物理研究所

安卓使用官方的demo好友问题

回复

IM即时通讯admin 回复了问题 • 2 人关注 • 1 个回复 • 113 次浏览 • 2021-01-21 18:17 • 来自相关话题

关于密码你不知道的冷知识?至今还有 3% 的人使用“123456”作为密码

科技创新fanta2 发表了文章 • 0 个评论 • 79 次浏览 • 2021-02-04 10:11 • 来自相关话题

之前我们报导过 2020年被用烂大街的密码《2020 最烂密码 TOP 200 曝光!》,500 多万个泄漏密码表明,共有近 3% 的人使用“123456”作为密码。而最近知名黑客网站 Have I Been Pwned 上一个密码“ji32k7au4a83”... ...查看全部

之前我们报导过 2020年被用烂大街的密码《2020 最烂密码 TOP 200 曝光!》,500 多万个泄漏密码表明,共有近 3% 的人使用“123456”作为密码。而最近知名黑客网站 Have I Been Pwned 上一个密码“ji32k7au4a83”的使用次数引起了热烈讨论。

Have I Been Pwned 是一个可以查询用户的邮箱是否被泄漏的网站,它的一个密码查询功能 Pwned Passwords 记录着在数据泄露中暴露的 551 509 767 个真实密码,用户可以在这里查询某个密码被使用的次数。比如查询一下 2018 年最烂密码“123456”,得到 23 174 662 次的结果:

1.jpg

“123456”这样简单易记的数字串被很多人作为密码使用,这很容易理解,但是有一个密码 “ji32k7au4a83”的使用次数让人费解,并且在最近引起了热烈讨论。

硬件/软件工程师 Robert Ou 最开始在 Pwned Passwords 上发现“ji32k7au4a83”竟然有 141 次使用,他觉得这不太正常,因为这个字符串太“偏”了,于是他在社区提问。

随后就有人指出了原因(估计就是使用者),原来“ji32k7au4a83”是汉语注音符号系统中“我的密码”的对应字符串。

汉语注音符号是以章太炎的记音字母作蓝本,1913 年由中国读音统一会制定,1918 年北洋政府教育部正式颁行的一套注音系统。

2.png

1918 年第一个版本如下:

声母介音韵母
ㄍㄎㄫㄐㄑㄬㄉㄊㄋㄅㄆㄇㄈㄪㄗㄘㄙㄓㄔㄕㄏㄒㄌㄖㄧㄨㄩㄚㄛㄝㄟㄞㄠㄡㄢㄤㄣㄥㄦ

经过一个多世纪的发展,目前注音符号已经变化不少。目前中国台湾、澎湖、金门与马祖仍在使用注音符号,详情可以查看百科。

3.jpg

以上中招的朋友看完本文请记得修改密码。

据说,黑客会根据网民习惯设置的密码,收集起来编写成为“常用密码库”,直接使用现成的常用密码库去匹配破解,往往就能短时间破解大量账号。有安全机构研究显示,黑客大约只要17分钟就可以破解1000个帐号。

那我们平时该如何去设置密码呢?

安全专家的建议是,设置密码满足这三点:

  1. 密码长度最好8位或以上;

  2. 密码没有明显的组成规律;

  3. 尽量使用三种以上符号,如“字母+数字+特殊符号”。


简单介绍融云 imkit 包含功能

IM即时通讯dht1212 发表了文章 • 0 个评论 • 220 次浏览 • 2021-01-08 11:13 • 来自相关话题

说明本篇文章简单介绍一下融云 imkit 包含的功能,大家可在阅读之后来对大体内容有一个基础的了解。详细内容还请翻阅 官方文档基本内容融云 imkit 是为了方便开发者快速集成而开发的一套 UI 库,里面主要包含三部分内容:用户信息会话列表会话页面用... ...查看全部

说明

本篇文章简单介绍一下融云 imkit 包含的功能,大家可在阅读之后来对大体内容有一个基础的了解。详细内容还请翻阅 官方文档

基本内容

融云 imkit 是为了方便开发者快速集成而开发的一套 UI 库,里面主要包含三部分内容:

  • 用户信息

  • 会话列表

  • 会话页面

image.png

用户信息

image.png

用户信息是指融云 SDK 提供了一套完整的用户信息的显示与存储机制,里面包含了用户信息、群信息、群名片信息,开发者仅仅需要将自己 App Server 内的用户信息包装成一个融云的特有的用户信息对象,然后传递给对应的接口即可。之后融云 SDK 会自己帮助显示对应用户的用户信息,并存储在本地数据库,方便后续的读取。

大体的显示流程可参考官方的流程图:

image.png

会话列表

image.png

会话列表是融云 SDK 根据消息生成的一个 list

当收到一条新消息并且会话列表没有当前会话的时候,SDK 会自动生成一条新的会话数据,并添加到 tableview 中。

需要注意的是融云的会话列表不会存储在服务器中,只在本地存储。当切换设备时,需要去融云开发者后台开通多设备同步,这样在新设备登录的时候,会触发融云的消息补偿,当移动端接收到消息的时候,会在新设备也生成一个新的会话列表。正常情况就是你设置几天就补偿几天。

支持的功能有:

  • 会话置顶

  • 会话删除

  • 会话免打扰

  • 已读回执显示

  • 有人@显示

会话页面

image.png

融云的会话页面整体可分为两部分

  • 消息展示区

  • 输入框

消息展示

消息展示就是当前用户收发消息展示的地方,和常规 APP 一样,接收在左边,发送在右边。发送方是不显示昵称的,接收方可根据配置来选择是否显示昵称。

SDK 自带的消息展示有

  • 文本消息

  • 语音消息

  • 图片消息

  • GIF 消息

  • 视频消息

  • 位置消息

  • 文件消息

  • 小灰条消息

开发者还能根据自己的需求来自定义其他消息,自定义消息有两种用法:

  1. 发送其他需要展示的消息,对应绑定一套 UI 组件,收到消息后,融云SDK 帮助你把绑定的这套 UI 展示到界面上。

  2. 当做控制消息:控制消息就是你不展示到界面上,但是你可以利用消息机制来做处理,从服务器或者其他地方下发一个指令,收到这个消息后,UI不会发生任何变化。但你可以根据这条消息来处理你的业务操作。比如刷新某个页面,获取某个信息等等

开发者可以继承融云的会话页面,在其子类来进行其他操作,在进入会话页面的时候,SDK 会自动拉取当前会话的历史聊天记录,这个操作会先从本地数据库获取,如果不够 10 条的话,会从服务器获取,需要开通历史消息云存储功能。(融云 SDK 会在本地搭建一套数据库,用来存储你所有的聊天内容)

在获取到历史记录之后,SDK 会自动帮你展示到 消息展示区。展示出来的消息都支持以下功能:

  • 发送出去的消息支持已读回执(单群聊)

  • 消息撤回:kit 默认为两分钟

  • 消息多选

  • 消息转发:支持合并转发

  • 消息引用

  • 消息删除

输入框

SDK 提供的输入框共分为四部分:

  • 文本输入

  • 语音输入

  • 表情

  • 扩展板

文本输入:

文本输入支持用户输入任何文本

群聊输入 @ 可触发@ 人功能

语音输入:

语音输入分为普通语音消息高清语音消息

高清语音消息是在2.9.25之后支持的。建议使用此套方案。

表情:
SDK 有一套默认的 emoji 表情,且支持表情自定义。

扩展板:

扩展板也就是 + 号区域,SDK 默认支持的功能有:

  • 音视频(需要集成 融云音视频,集成之后会自动出现)

  • 照片

  • 位置:支持位置实时共享

  • 语音输入:需要使用科大讯飞的库来做,融云提供了相关的库内容

  • 名片

  • 文件:文件消息是 SDK 下载到指定目录的文件,并非手机系统的文件。

  • 其他:自定义内容,可根据自己的业务添加其他的小内容。

此外,SDK还提供了 常用语功能,目前仅支持单聊,且字数在 30 字以内,可进行类似快捷回复之类的操作。

其他内容,后续补充。

更多详细的内容可自己阅读官网文档。


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

IM即时通讯dht1212 发表了文章 • 0 个评论 • 218 次浏览 • 2021-01-08 11:13 • 来自相关话题

升级 Xcode12 后,模拟器都是 iOS14 了,运行自己的项目,到了聊天页面就崩溃,具体崩溃信息为Thread 1: “-[_UIPageControlIndicatorContentView setImage:]: unrecognized selec... ...查看全部

升级 Xcode12 后,模拟器都是 iOS14 了,运行自己的项目,到了聊天页面就崩溃,具体崩溃信息为Thread 1: “-[_UIPageControlIndicatorContentView setImage:]: unrecognized selector sent to instance。

从崩溃看,猜测是因为缺少了某个方法导致了崩溃,提了个工单给融云,他们的技术支持服务还是挺到位的,很快给了答复,他们对这个问题已经做了及时处理(其实他们已经发了站内信和邮件,自己没注意),更新了官网下载和 pod 上的 SDK,卸载现有 SDK,重新下载 2.10.4 以上版本 SDK 就可以了。

并且这个崩溃只针对使用 imkit的,使用 imlib 的用户没事儿,下面是他们在工单里面回复的具体内容:

通过 Xcode 12 打包 App,在 iOS 14 版本中我司发现 2.x 、4.0.0 、4.0.0.1 版本的 IMKit SDK 与 iOS 14 出现了兼容问题,该问题会引起 App 崩溃,针对该问题进行了紧急的修复
您可以在如下版本获得修改后的 SDK 版本:
1、iOS IMKit SDK 版本为 4.0.0 和 4.0.0.1 的客户务必升级至 4.0.1+ 版本。
2、iOS IMKit SDK 版本为 2.x 客户务必升级至 2.10.6-DEV 或者 2.10.4-Stable( 9 月 18 日发布的版本),这两个版本已针对该问题进行了修复
(1)请务必于 2020 年 9 月 18 日以后重新下载获取 SDK,更新您的 App,下载地址:https://www.rongcloud.cn/downloads/history/ios
(2)使用 Pod 集成的客户,请参考文档先清理 Pod 缓存,文档地址:https://docs.rongcloud.cn/v4/views/im/ui/guide/private/setting/include/ios.html#cocoapods-clean
3、基于 iOS IMLib SDK 集成的客户不受影响。


小吐槽一下,这个崩溃问题虽然发了站内信和邮件,但还是不太容易一起注意,如果能在下面的官网或文档里面有个弹窗或浮动提示就更好了

融云官网:https://www.rongcloud.cn/
融云文档:https://docs.rongcloud.cn/v4


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

IM即时通讯赵炳东 发表了文章 • 0 个评论 • 231 次浏览 • 2021-01-05 10:55 • 来自相关话题

1、必备资料 github AMR 开源库 :https://github.com/jpemartins/amr.js 用心把这个项目看一遍,对于我下面说的话,可以忽略啦,代码是最好的文章,哈哈~~2、核心 JS 库 :amr.js 、pcmdata... ...查看全部

1、必备资料 github AMR 开源库 :https://github.com/jpemartins/amr.js 用心把这个项目看一遍,对于我下面说的话,可以忽略啦,代码是最好的文章,哈哈~~

2、核心 JS 库 :amr.js 、pcmdata.min.js、libamr-nb.js (g上述ithub项目中有另外三个js,我给合成一个amr.js,不要混乱)这三个 JS 是播放声音的主要依赖,下面一 一 介绍下:

① amr.js : 可以理解成 “桥” 的概念,连接底层解码和客户端调用的桥梁。

② pcmdata.min.js : 封装 pcm data, PCM是一种编码格式 , PCM 添加上 RIFF 头 可以组成 Wave 音频(Wave 不止这一种编码方式),细节太深,小弟也不懂啦~~,有空深挖。

③ libamr-nb.js : amr转换的核心库,这个文件比较大,压缩后大概也要 600kb 左右。

④ 如果下载三个核心库,建议从上篇 “Web 播放声音 — 介绍篇” 中的附件中卸载,在 github 现在的核心库,在 Chrome 下只能播放一次,把这个问题修复了一下,原因是以同样的 Audio.src 创建 Audio 在 Chrome 下就不能二次播放,网上说法各异,最终自己动手解决了,详细可以在 amr.js 中的 global.util.play 方法中看。

3、具体用法

说了有一阵子白话了,该上点代码了,下面是我封装的播放 AMR 格式 Base64 码 的插件,直接在页面引用可以 ,下载地址可以在 “Web 播放声音 — 介绍篇” 下载。

页面一点样式都没有加,只有两个按钮,小伙伴凑合凑合,哈哈~~

代码中只引入了 voice.js 一个 js,其他 js 动态引用了。(公司有 cdn 所以直接放在上面了)。

步骤:

第一步:执行初始化。

第二步:调用相应方法。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Example</title>
<script src="js/voice.js"></script>
</head>
<body>
<input type="button" value="play" id="playId"/>
<input type="button" value="stop" id="stopId"/>
</body>
<script type="text/javascript">
//初始化
RongIMLib.RongIMVoice.init();
//播放
document.getElementById("playId").onclick = function(){
   // base64Str : amr 格式的 base64 码,可以用 HTML5 FileReader 或者canvas 进行转换 。 
   var base64Str = "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";
    RongIMLib.RongIMVoice.play(base64Str );
};
//暂停
document.getElementById("stopId").onclick = function(){ RongIMLib.RongIMVoice.stop(); }; 
</script> 
</html>

在这里也把 “voice.js” 的代码贴出来,使用 typescript 写完后生成出来的,方便小伙伴们查看:

var RongIMLib;
(function (RongIMLib) {
 var RongIMVoice = (function () {
  function RongIMVoice() {
  }
  /**
  * 初始化声音库
  */
  RongIMVoice.init = function () {
      if (this.isIE) {
          var div = document.createElement("div");
          div.setAttribute("id", "flashContent");
          document.body.appendChild(div);
          var script = document.createElement("script");
          script.src = "http://cdn.ronghub.com/swfobject-2.0.0.min.js";
          var header = document.getElementsByTagName("head")[0];
          header.appendChild(script);
          setTimeout(function () {
              var swfVersionStr = "11.4.0";
              var flashvars = {};
              var params = {};
              params.quality = "high";
              params.bgcolor = "#ffffff";
              params.allowscriptaccess = "always";
              params.allowfullscreen = "true";
              var attributes = {};
              attributes.id = "player";
              attributes.name = "player";
              attributes.align = "middle";
              swfobject.embedSWF("http://cdn.ronghub.com/player-2.0.2.swf", "flashContent", "1", "1", swfVersionStr, null, flashvars, params, attributes);
          }, 200);
      }
      else {
          var list = ["http://cdn.ronghub.com/pcmdata-2.0.0.min.js", "http://cdn.ronghub.com/libamr-2.0.1.min.js"];
          for (var i = 0, len = list.length; i < len; i++) {
              var script = document.createElement("script");
              script.src = list[i];
              document.head.appendChild(script);
          }
      }
      this.isInit = true;
  };
  /**
  * 开始播放声音
  * @param data {string} amr 格式的 base64 码
  * @param duration {number} 播放大概时长 用 data.length / 1024
  */
  RongIMVoice.play = function (data, duration) {
      this.checkInit("play");
      var me = this;
      if (me.isIE) {
          me.thisMovie().doAction("init", data);
      }
      else {
          me.palyVoice(data);
          me.onCompleted(duration);
      }
  };
  /**
  * 停止播放声音
  */
  RongIMVoice.stop = function () {
      this.checkInit("stop");
      var me = this;
      if (me.isIE) {
          me.thisMovie().doAction("stop");
      }
      else {
          if (me.element) {
              me.element.stop();
          }
      }
  };
  /**
  * 播放声音时调用的方法
  */
  RongIMVoice.onprogress = function () {
      this.checkInit("onprogress");
  };
    /** 
     * 校验是否初始化
      */
  RongIMVoice.checkInit = function (postion) {
      if (!this.isInit) {
          throw new Error("RongIMVoice not initialized,postion:" + postion);
      }
  };
  RongIMVoice.thisMovie = function () {
      return eval("window['player']");
  };
  RongIMVoice.onCompleted = function (duration) {
      var me = this;
      var count = 0;
      var timer = setInterval(function () {
          count++;
          me.onprogress();
          if (count >= duration) {
              clearInterval(timer);
          }
      }, 1000);
      if (me.isIE) {
          me.thisMovie().doAction("play");
      }
  };
  RongIMVoice.base64ToBlob = function (base64Data, type) {
      var mimeType;
      if (type) {
          mimeType = { type: type };
      }
      base64Data = base64Data.replace(/^(.*)[,]/, '');
      var sliceSize = 1024;
      var byteCharacters = atob(base64Data);
      var bytesLength = byteCharacters.length;
      var slicesCount = Math.ceil(bytesLength / sliceSize);
      var byteArrays = new Array(slicesCount);
      for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
          var begin = sliceIndex * sliceSize;
          var end = Math.min(begin + sliceSize, bytesLength);
          var bytes = new Array(end - begin);
          for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
              bytes[i] = byteCharacters[offset].charCodeAt(0);
          }
          byteArrays[sliceIndex] = new Uint8Array(bytes);
      }
      return new Blob(byteArrays, mimeType);
  };
  RongIMVoice.palyVoice = function (base64Data) {
      var reader = new FileReader(), blob = this.base64ToBlob(base64Data, "audio/amr"), me = this;
      reader.onload = function () {
          var samples = new AMR({
              benchmark: true
          }).decode(reader.result);
          me.element = AMR.util.play(samples);
      };
      reader.readAsBinaryString(blob);
  };
  RongIMVoice.isIE = /Trident/.test(navigator.userAgent);
  RongIMVoice.isInit = false;
  return RongIMVoice;
})();
RongIMLib.RongIMVoice = RongIMVoice;
//兼容AMD CMD
if ("function" === typeof require && "object" === typeof module && module && module.id && "object" === typeof exports && exports) {
  module.exports = RongIMVoice;
}
else if ("function" === typeof define && define.amd) {
  define("RongIMVoice", [], function () {
      return RongIMVoice;
  });
}
})(RongIMLib || (RongIMLib = {}));

好啦,到此本文结束,会尽快更新 Flash 下播放 AMR 格式 Base64 码,代码中哪里有错误或者有异议麻烦小伙伴们联系我,一起讨论,有则改之无法加冕,一起进步,thx~~。

以上是融云 SDK 里的源码,不过最近推出了直接使用 Audio 方式播放 AAC 格式的音频文件,非常方便

原始文章连接:https://www.cnblogs.com/yuhongda0315/p/5224188.html

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

融云文档:https://docs.rongcloud.cn/v4


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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 387 次浏览 • 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 即可.


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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 403 次浏览 • 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) {
        }
    });

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

融云清空历史消息 Android 端

IM即时通讯大神庵 发表了文章 • 0 个评论 • 400 次浏览 • 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();
试下。


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

IM即时通讯大神庵 发表了文章 • 0 个评论 • 360 次浏览 • 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;
             }
    }).


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

IM即时通讯徐凤年 发表了文章 • 0 个评论 • 366 次浏览 • 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 个评论 • 155 次浏览 • 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, 从我的闪屏页跳转到相应的会话界面去. 这样需求就搞定了.