前言

  • 上文讲了下要去做哪些事,重点分析了融云Sdk中RongExtension这个扩展控件,本文来学习下同样是融云Sdk中的AutoRefreshListView如何适配多种消息的实现方式,写的有不足之处还望指出。

AutoRefreshListView如何适配多种消息

本文不分析AutoRefreshListView内部源码,从数据适配角度分析如何适配上文讲到的多种聊天消息

既然从AutoRefreshListView开始,那先来了解下一般使用ListView的步骤:

  • 布局器寻找ListView控件,通过findViewById方法
  • 创建数据适配器
  • ListView设置数据适配器与常用事件
  • 新增数据到适配器并更新UI

    但是数据更新到UI,会遇到多种不同数据结构(多种消息类型),那么能不能找到一种简洁的方法,让不同消息交给不同的消息处理者,以此来达到解耦的目的。那他是如何做到的?

    这才引出本文分析的重点:MessageListAdapter;

MessageListAdapter概述

  • MessageListAdapter继承自BaseAdapter<UIMessage>BaseAdapter<T>泛型类重点分析下getView(int position, View convertView, ViewGroup parent)方法;
  • 其中两个抽象方法newView与bindView,看名字有点头绪是干嘛的,newView是创建新的View,bindView是绑定数据到View;
  • 怎么使用上面的抽象方法?判断下convertView对象,如果为空,调用newView方法,否则,赋值给临时变量view,最后把数据绑定到view上,并返回view对象。
 @Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
view = this.newView(mContext, position, parent);
}
this.bindView(view, position, this.getItem(position));
return view;
} protected abstract View newView(Context context, int pos, ViewGroup parent); protected abstract void bindView(View convertView, int pos, T t);

MessageListAdapter的bindView方法

  • 继承自抽象类BaseAdapter需要实现两个方法newView与bindView;newView使用ViewHolder进行控件创建;
  • bindView消息数据与消息布局绑定通过了下面代码来实现的;这段代码中涉及到provider与contentView对象,其中provider对象实现了接口IContainerItemProvider,而contentView对象是ProviderContainerView的实例,下面详解这两个类。
   final View view = holder.contentView.inflate((IContainerItemProvider)provider);
((IContainerItemProvider)provider).bindView(view, position, data);
1.IContainerItemProvider接口与及其子类
  • 下面画了相关类的UML类图,省略了部分子类;

1.1如何获取provider对象
  • 贴上获取provider代码,讲下基本的思路:
  • 1.判断消息是否是评论消息,如果不是,则根据消息类型获取对应消息类型的provider;
  • 2.如果provider为null,则匹配为未知消息类型
  • 3.如果provider还是为空,则返回;否则,返回provider对象;
  if(data != null) {
final MessageListAdapter.ViewHolder holder = (MessageListAdapter.ViewHolder)v.getTag();
if(holder == null) {
RLog.e("MessageListAdapter", "view holder is null !");
} else {
Object provider;//声明provider对象
ProviderTag tag;
//判断是否是评论消息
if(this.getNeedEvaluate(data)) {
provider = RongContext.getInstance().getEvaluateProvider();
tag = RongContext.getInstance().getMessageProviderTag(data.getContent().getClass());
} else {
if(RongContext.getInstance() == null || data == null || data.getContent() == null) {
RLog.e("MessageListAdapter", "Message is null !");
return;
} provider = RongContext.getInstance().getMessageTemplate(data.getContent().getClass());
if(provider == null) {
provider = RongContext.getInstance().getMessageTemplate(UnknownMessage.class);
tag = RongContext.getInstance().getMessageProviderTag(UnknownMessage.class);
} else {
tag = RongContext.getInstance().getMessageProviderTag(data.getContent().getClass());
} if(provider == null) {
RLog.e("MessageListAdapter", data.getObjectName() + " message provider not found !");
return;
}
}
  • 还是没看到是如何获取provider对象的,别着急,下面让我们看看getMessageTemplate方法,看是如何通过消息对象获取对应的消息provider*
  • 看第一行代码发现:是通过mWeakTemplateMap获取到provider,查看声明发现是HashMap类型(弱引用),这个hashmap对象的数据是怎么来的?
  • 看下面 (MessageProvider)((MessageProvider)this.mTemplateMap.get(type)).clone();这段代码发现与上面有什么不同的地方;一个是HashMap对象换成了mTemplateMap,另一个是调用了clone(由于实现了cloneable接口);
  • this.mWeakTemplateMap.put(type, provider);则是把clone的对象放到mWeakTemplateMap对象中,也解释前面讲的mWeakTemplateMap对象的数据是怎么来的。

Why?为什么需要两个HashMap对象以及clone方法调用的原因。

下面一步一步分析看,首先mTemplateMap对象数据哪里来的?这个是在融云建立连接成功回调以后添加的消息模板;

  • 1.反着来看如果不复制(调用clone())的话,provider 对象是给外部使用;那么退出聊天界面这个对象将被销毁的,当再次进入聊天界面后mTemplateMap对象存放的MessageProvider为空了,所以显然mTemplateMap注册的MessageProvider是建立连接后长期的;

    这样的话,不管复制的MessageProvider怎么操作,母体都不会影响。
  • 2.在聊天界面有可能发了多条重复或者类型相同的消息,那么是不是可以避免重复复制,毕竟复制需要时间与空间代价,所以可以重复使用那些还未被销毁的MessageProvider(弱引用对象),这样可以重复使用又不会出现潜在的内存泄漏。
  • 到此对于如何获取provider对象的实现有了个具体的了解。
    public MessageProvider getMessageTemplate(Class<? extends MessageContent> type) {
MessageProvider provider = (MessageProvider)this.mWeakTemplateMap.get(type);
if(provider == null) {
try {
if(this.mTemplateMap != null && this.mTemplateMap.get(type) != null) {
provider = (MessageProvider)((MessageProvider)this.mTemplateMap.get(type)).clone();
this.mWeakTemplateMap.put(type, provider);
} else {
RLog.e("RongContext", "The template of message can\'t be null. type :" + type);
}
} catch (CloneNotSupportedException var4) {
var4.printStackTrace();
}
} return provider;
}
2.contentView对象--自定义ProviderContainerView
  • contentView是属于ProviderContainerView,ProviderContainerView布局继承自FrameLayout
  • 自定义布局控件提供了一个重要的方法public <T extends IContainerItemProvider> View inflate(T t)与两个HashMap:mViewCounterMap--记录使用频率与mContentViewMap--缓存控件
  • 1.其他代码先不看,先来分析下最后一个判断result==null的代码,整理思路是newView将得到view添加到ProviderContainerView当前容器中并返回view,当然这里同样做了缓存。
 public <T extends IContainerItemProvider> View inflate(T t) {
View result = null;
if(this.mInflateView != null) {
this.mInflateView.setVisibility(GONE);
} if(this.mContentViewMap.containsKey(t.getClass())) {
result = (View)this.mContentViewMap.get(t.getClass());
this.mInflateView = result;
((AtomicInteger)this.mViewCounterMap.get(t.getClass())).incrementAndGet();
} if(result != null) {
if(result.getVisibility() == GONE) {
result.setVisibility(VISIBLE);
} return result;
} else {
this.recycle();
result = t.newView(this.getContext(), this);
if(result != null) {
super.addView(result);
this.mContentViewMap.put(t.getClass(), result);
this.mViewCounterMap.put(t.getClass(), new AtomicInteger());
} this.mInflateView = result;
return result;
}
}
  • 2.那么问题来了:recycle方法是用来干啥的?缓存View是如何实现的?
  • 先来看下recycle方法,其中容易发现mMaxContainSize=3是限定条件,超过或者相等则会进行移除控件,那如果移除的话应该怎么移除是最优的?
  • 这里用到了最近最少使用算法,也就是如果这个控件很久没使用了那么下次用到可能性相对来说比较小,那么超过限定条件mMaxContainSize>=3后,应该先删除这个控件。
  • 那他是如何做到不同控件的使用频率的呢?这要回到inflate方法,根据传入的消息处理者类型,如果mContentViewMap中存在了对应控件,mViewCounterMap找到对应键并自动+1(这里的键的类型是AtomicInteger,自增或者自是减线程安全的),那数值大的代表最近刚刚使用过;如果mContentViewMap不存在的话,则把消息处理器添加到mContentViewMap与mViewCounterMap两个HashMap中,起到缓存作用。通过以上两步,使缓存效率得到优化。
 private void recycle() {
if(this.mInflateView != null) {
int count = this.getChildCount();
if(count >= this.mMaxContainSize) {
Map.Entry min = null; Map.Entry item;
for(Iterator view = this.mViewCounterMap.entrySet().iterator(); view.hasNext(); min = ((AtomicInteger)min.getValue()).get() > ((AtomicInteger)item.getValue()).get()?item:min) {
item = (Map.Entry)view.next();
if(min == null) {
min = item;
}
} this.mViewCounterMap.remove(min.getKey());
View view1 = (View)this.mContentViewMap.remove(min.getKey());
this.removeView(view1);
} }
}
  • 先讲到这里,后续会分析插件功能。。。

学问Chat UI(2)的更多相关文章

  1. 学问Chat UI(3)

    前言 上文学问Chat UI(2)分析了消息适配器的实现; 本文主要学习下插件功能如何实现的.并以图片插件功能作为例子详细说明,分析从具体代码入手; 概要 分析策略说明 "+"功能 ...

  2. 学问Chat UI(1)

    前言 由于项目需要,最近开始借鉴学习下开源的Android即时通信聊天UI框架,为此结合市面上加上本项目需求列了ChatUI要实现的基本功能与扩展功能. 融云聊天UI-Android SDK 2.8. ...

  3. 学问Chat UI(4)

    前言 写这个组件是在几个月前,那时候是因为老大讲RN项目APP的通讯聊天部分后面有可能自己实现,让我那时候尝试着搞下Android通讯聊天UI实现的部分,在这期间,找了不少的Android原生项目:蘑 ...

  4. 77.Android之代码混淆

    转载:http://www.jianshu.com/p/7436a1a32891 简介 作为Android开发者,如果你不想开源你的应用,那么在应用发布前,就需要对代码进行混淆处理,从而让我们代码即使 ...

  5. 【SignalR学习系列】5. SignalR WPF程序

    首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...

  6. 如何用ABP框架快速完成项目(8) - 用ABP一个人快速完成项目(4) - 能自动化就不要手动 - 使用自动化测试(BDD/TDD)

    做为一个程序员, 深深知道计算机自动化的速度是比人手动的速度快的, 所以”快速”完成项目的一个重要武器就是: 能自动化就不要手动.   BDD/TDD有很多优势, 其中之一就是自动化, 我们这节文章先 ...

  7. Android: apk反编译 及 AS代码混淆防反编译

    一.工具下载: 1.apktool(资源文件获取,如提取出图片文件和布局文件) 反编译apk:apktool d file.apk –o path 回编译apk:apktool b path –o f ...

  8. 带你彻底明白 Android Studio 打包混淆

    前言 在使用Android Studio混淆打包时,该IDE自身集成了Java语言的ProGuard作为压缩,优化和混淆工具,配合Gradle构建工具使用很简单.只需要在工程应用目录的gradle文件 ...

  9. “四核”驱动的“三维”导航 -- 淘宝新UI(需求分析篇)

    前言 孔子说:"软件是对客观世界的抽象". 首先声明,这里的"三维导航"和地图没一毛钱关系,"四核驱动"和硬件也没关系,而是为了复杂的应用而 ...

随机推荐

  1. ArcGIS API for JavaScript 4.4学习笔记[新] AJS4.4和AJS3.21新特性

    ESRI官网悄无声息突然更新4.4和3.21,公众号也没有什么消息.照例,给大家看看这次更新有什么新特性吧. 1. AJS 4.4 官方更新日志:点我,比较详细.我在这里抽一些主干作为说明. 1.1 ...

  2. 计算机浏览器存储技术cookie、sessionStorage、localStorage

    HTTP无状态协议是指协议对于事务处理没有记忆能力.会话跟踪协议的状态是指下一次传输可以"记住"这次传输信息的能力,无状态是指同一个会话(注意什么叫同一个会话)的连续两个请求互相不 ...

  3. CentOS下源码安装vsftpd-3.0.0,并设置指定用户访问指定目录(附带完整配置文件)

    1.卸载系统已经存在的ftp服务器 因为是源码安装,所以不能通过rpm -qa的方式查看是否已经安装ftp服务器,可以通过find / | grep vsftp*方式查看系统中存在哪些与vsftpd相 ...

  4. 平板点餐软件编程体会---记我的Android编程之路

    前言 想开发一个平板点餐系统,研究下陈江根大侠分享的一个很高水准的实例,只是个单机版无实用意义. (如需运行源码请回复联系邮箱) 实现 Mysql 数据库+Tomcat WEb服务器,使用Servle ...

  5. MongoDB入门解析【学习记录】

    刚开始学习mongodb,对笔记做了一个整理.是基于nodejs来学习的. 1.mongodb介绍 mongodb 是C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添 ...

  6. git - 远程分支

    对于用户来说,git给人提交到本地的机会.我们可以在自己的机器上创建不同的branch,来测试和存放不同的代码. 对于代码管理员而言,git有许多优良的特性.管理着不同的分支,同一套源代码可以出不一样 ...

  7. Linux基础(三)

    一.正文处理命令及tar命令 1.文件合并 cat a.txt b.txt > c.txt 2.打包 归档命令tar可以把多个文件打包成一个文件 如tar cvf test.tar a.txt ...

  8. 途虎养车Tuhu商城系统开发

    途虎养车Tuhu商城系统开发,咨询:何经理152-2217-7508(微信同号)途虎养车商城小程序开发,途虎养车商城小程序平台开发,途虎养车商城小程序系统开发. 为什么能做得这么好,里面的门道确实不少 ...

  9. JS浏览器对象:window对象、History、Location对象、Screen对象

    一.JS浏览器对象-window 1.window对象 window对象是BOM的核心,window对象指当前的浏览器窗口 所有JavaScript全局对象.函数以及变量均自动成为window对象的成 ...

  10. HTML5 开发APP(打开相册以及图片上传)

    我们开发app,常常会遇到让用户上传文件的功能.比如让用户上传头像.我公司的业务要求是让用户上传支付宝收款二维码,来实现用户提现的功能.想要调用相册要靠HTML Plus来实现.先上效果图 基本功能是 ...