本文由“猫爸iYao”原创分享,感谢作者。

1、引言

最近有个需求:评论@人(没错,就是IM聊天或者微博APP里的@人功能),就像下图这样:

 
▲ 微信群聊界面里的@人功能 
 
▲ QQ群聊界面里的@人功能

网上已经有一些文章分享了类似功能实现逻辑,但是几乎都是扩展EditText类,这种实现方式肯定不能进入我的首发阵容。你以为是因为它不符合面向对象六大原则?错,只因为它不够优雅!不够优雅!不够优雅!

那么,只有饮水机代码怎么办?当然是:

read the fuking source code

功夫不负有心人,我读了一遍EditText源码,然后就造出了这个“优雅的”轮子(开玩笑,EditText源码怎么能叫fuking source code,他有一个爸爸叫TextView)。废话不多说,上酸菜。

在此之前,你需要记住一个跟文本相关的思想:一切皆Span!

学习交流:

- 即时通讯/推送技术开发交流4群:101279154 [推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

(本文同步发布于:http://www.52im.net/thread-2165-1-1.html

2、添加标签文本样式,并与标签的业务数据绑定

所有人都知道文本样式与Spannable有关。

这里同样使用Spannable,我定义了一个DataBindingSpan<T>接口,主要有两个功能:

1)让用户提供一个CharSequence对象作为标签,它决定了标签文本的样式和内容;

2)提供一个方法返回DataBindingSpan对象所绑定的业务数据。

interfaceDataBindingSpan<T> {

fun spannedText(): CharSequence

fun bindingData(): T

}

示例代码:

class SpannableData(privateval spanned: String): DataBindingSpan<String> {

override fun spannedText(): CharSequence {

return SpannableString(spanned).apply {

setSpan(ForegroundColorSpan(Color.RED), 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

}

}

override fun bindingData(): String {

returnspanned

}

}

这个类仅仅包装了一个字符串,spannedText()返回一个改变标签文本颜色为红色的字符串,同时 bindingData()将该字符串作为业务数据返回。

你也可以把它换成其他的,user对象不错。spannedText()返回username,bindingData()返回userId,你就可以轻松实现@人功能业务数据绑定相关的逻辑了。

3、保证文本上绑定的数据的安全可靠

当我们把Span绑定到文本上以后,我们需要在文本发生变化时,保证文本和数据的安全性,可靠性,一致性。

其实从DataBindingSpan开始,我们就在处理这个事情了。正如SpannableData所展现的一样,当spannedText()返回的是一个Spannable对象时,使用Spanned.SPAN_EXCLUSIVE_EXCLUSIVE作为flag。它不能在头部和尾部扩展Span的范围,只允许中间插入。同时,当Span覆盖的文本被删除时,Span也会被删除。也就是说,它天生具有一定数据安全可靠的属性。这会为我们省掉很多事情。

当然,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE并不具备完全的安全性。毕竟它不能阻止中间插入。这个事情得我们自己来做。那么,为了禁止中间插入,我们应该怎么做呢?

这个需求又产生了两个问题:

1)当普通文本发生变化后,如何监控一个Span起始位置发生变化?

2)如何禁止Span内部插入光标?

对于第一个问题,我在网上看到过一种思路。维护一个Span起始位置管理器SpanRangeManager,然后利用TextWather监听文本变化,文本的任何变化都会导致SpanRangeManager重新测算Span的位置。

当然,如果我使用这种方式,就不会有这篇博客了。其实Android SDK便有一个优秀的Span管理器,那就是SpannableStringBuilder。同时SDK提供了一个侦听器SpanWatcher侦听SpannableStringBuilder中Span的变化。有兴趣的同学可以去看一看他的源码。

第二个问题,我们要保证文本与数据的一致性,禁止光标插入到Span覆盖的文本中间。

有三种做法:

1)普通文本,当标签文本被破坏(删除、插入、追加文本)时,让绑定的数据失效,这就是微信的做法;

2)普通文本,把标签文本作为一个整体,不能对标签内部插入光标,杜绝数据被破坏的情况,这是微博的做法;

3)占位符,使用不可分割的Span(如ImageSpan)替换,这是QQ的做法。

微博、微信的方法都必须要对软键盘删除键、文本变化、光标活动、文本选中状态以及span变化进行监听和处理。QQ就简单多了,后面会讲到。

4、微博的做法

4.1 侦听并处理光标活动、选中状态以及Span位置变化

对于光标活动和选中状态侦听,如果采用继承EditText的方式实现标签文本功能,重写onSelectionChanged(int selStart, int selEnd)方法便能够侦听光标活动。但是,这种方式怎么能算优雅呢?

要想“优雅地”实现怎么办?还是那句话:

read the fuking source code

两个角色:

Selection

SpanWatcher

如果有一篇文章叫做《Selection如何管理文本光标活动和选中状态?》,那么它一定能回答这个问题。

这里不会详细讲述Selection内部实现,你只需要知道两点:

1)选中状态具有起点(start)和终点(end),而start与end反映在文本中,其实是两个NoCopySpan: START, END;

2)光标是一种特殊的选中状态,start与end在同一位置。

既然选中状态的实现是Span,它就是与View无关的,而与Spannable有关。也就是说,我们可以不使用EditText自身的API却能够管理它的光标活动和选中状态(请注意这几句话,他是“优雅实现”的基石)。

Selection管理光标活动。那么,SpanWatcher又是什么?前面说了,它是SpannableStringBuidler中用于侦听Span变化的监听器。有个东西和它很像,TextWatcher。没错,他俩有同一个爹NoCopySpan。他俩一个侦听文本变化,一个侦听Span变化。

下面是SpanWatcher的源码:

/**

* When an object of this type is attached to a Spannable, its methods

* will be called to notify it that other markup objects have been

* added, changed, or removed.

*/

public interface SpanWatcher extendsNoCopySpan {

/**

* This method is called to notify you that the specified object

* has been attached to the specified range of the text.

*/

public void onSpanAdded(Spannable text, Object what, intstart, intend);

/**

* This method is called to notify you that the specified object

* has been detached from the specified range of the text.

*/

public void onSpanRemoved(Spannable text, Object what, intstart, intend);

/**

* This method is called to notify you that the specified object

* has been relocated from the range <code>ostart…oend</code>

* to the new range <code>nstart…nend</code> of the text.

*/

public void onSpanChanged(Spannable text, Object what, intostart, intoend, intnstart, intnend);

}

我们已经知道光标是一种Span。也就是说,我们可以通过SpanWatcher侦听光标活动,通过Selection实现当光标移动到Span内部时,让它重新移动到Span最近的边缘位置,Span内部永远无法插入光标。这样便能够实现把标签文本(spanned text)看作一个整体的思路。

下面是代码实现:

package com.iyao

import android.text.Selection

import android.text.SpanWatcher

import android.text.Spannable

import kotlin.math.abs

import kotlin.reflect.KClass

class SelectionSpanWatcher<T: Any>(privateval kClass: KClass<T>): SpanWatcher {

privatevar selStart = 0

privatevar selEnd = 0

override fun onSpanChanged(text: Spannable, what: Any, ostart: Int, oend: Int, nstart: Int, nend: Int) {

if(what === Selection.SELECTION_END && selEnd != nstart) {

selEnd = nstart

text.getSpans(nstart, nend, kClass.java).firstOrNull()?.run {

val spanStart = text.getSpanStart(this)

val spanEnd = text.getSpanEnd(this)

val index = if(abs(selEnd - spanEnd) > abs(selEnd - spanStart)) spanStart elsespanEnd

Selection.setSelection(text, Selection.getSelectionStart(text), index)

}

}

if(what === Selection.SELECTION_START && selStart != nstart) {

selStart = nstart

text.getSpans(nstart, nend, kClass.java).firstOrNull()?.run {

val spanStart = text.getSpanStart(this)

val spanEnd = text.getSpanEnd(this)

val index = if(abs(selStart - spanEnd) > abs(selStart - spanStart)) spanStart elsespanEnd

Selection.setSelection(text, index, Selection.getSelectionEnd(text))

}

}

}

override fun onSpanRemoved(text: Spannable?, what: Any?, start: Int, end: Int) {

}

override fun onSpanAdded(text: Spannable?, what: Any?, start: Int, end: Int) {

}

}

现在,我们只需要在setText()之前把这个Span添加到文本上就可以了。

4.2 侦听软键盘删除键并处理选中状态

现在已经把Span覆盖的文本作为一个整体,且无法插入光标,但是当我们从Span尾部删除文本,仍是逐字删除。我们的要求是删除Span文本时,能够整体删除整个Span,这就需要监听键盘删除键。

package com.iyao

import android.text.Selection

import android.text.Spannable

class KeyCodeDeleteHelper private constructor(){

companion object {

fun onDelDown(text: Spannable): Boolean {

val selectionStart = Selection.getSelectionStart(text)

val selectionEnd = Selection.getSelectionEnd(text)

text.getSpans(selectionStart, selectionEnd, DataBindingSpan::class.java).firstOrNull { text.getSpanEnd(it) == selectionStart }?.run {

return(selectionStart == selectionEnd).also {

val spanStart = text.getSpanStart(this)

val spanEnd = text.getSpanEnd(this)

Selection.setSelection(text, spanStart, spanEnd)

}

}

returnfalse

}

}

}

让我们使用它:

editText.setOnKeyListener { v, keyCode, event ->

if(keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN) {

return @setOnKeyListenerKeyCodeDeleteHelper.onDelDown((v as EditText).text)

}

return @setOnKeyListenerfalse

}

//取数据

val strings = editText.text.let {

it.getSpans(0, it.length, DataBindingSpan::class.java)

}.map { it.bindingData() }

现在就可以实现微博一样效果了。一切都那么顺利。

然而,当你运行起来会发现,SelectionSpanWatcher完全没有效果。轮子都造好了,你告诉我轴承断了。

并且,当你打印EditText文本上的Span时,你找不到SelectionSpanWatcher。这说明SelectionSpanWatcher在setText()过程中被清除掉了。那我们能不能把它放在setText()之后设置呢?如果你这么做,你会发现一个新问题。setText()添加的文本没有效果。似乎我们不能通过setText()添加内容,只能使用getText()追加内容。不仅如此,我们必须完全禁用setText(),因为每一次调用,都会清除掉SelectionSpanWatcher。

这种方式看起来还不错,但是换一个不熟悉这个特性的人来使用怎么办?告诉他不能用setText()方法?或者用内联方法或继承的方式为EditText新增一个方法? 这些都可以,唯一的缺点是,它不是我想要的优雅。我要让它就像使用普通EditText一样正常使用setText()方法。

需要思考的问题是,SelectionSpanWatcher在哪里消失了?我要重新找回这个轴承。

4.3 让轮子优雅实现的轴承:Editable.Factory

SelectionSpanWatcher在setText()方法中消失了。我需要去阅读它的源码。

EditText重写了getText()、setText(CharSequence text, BufferType type)方法:

@Override

public Editable getText() {

CharSequence text = super.getText();

// This can only happen during construction.

if(text == null) {

returnnull;

}

if(text instanceofEditable) {

return(Editable) super.getText();

}

super.setText(text, BufferType.EDITABLE);

return(Editable) super.getText();

}

@Override

public voidsetText(CharSequence text, BufferType type) {

super.setText(text, BufferType.EDITABLE);

}

从源码上看,重写的唯一目的是将BufferType设置为BufferType.EDITABLE。

我们都知道TextView有三种文本模式:

1)BufferType.NORMAL 静态文本模式,这种模式的文本无法编辑,也没有富文本样式;

2)BufferType.SPANNABLE 带文本样式的模式,不可编辑。当TextView.isTextSelectable()返回true时,TextView的文本模式;

3)BufferType.EDITABLE EditText的文本模式,可编辑,带文本样式。

这里不具体讲这三种模式相关的内容。只需要知道EditText的模式是BufferType.EDITABLE。

那么,BufferType.EDITABLE与“轴承”又有什么关系呢? 确实有关系。

阅读上面的源码片段时,不知道有没有人注意到setText(CharSequence)传入一个CharSequence对象,TextView#getText()返回的是CharSequence对象, EditText#getText()却返回一个Editable对象。它是在什么时候,如何完成的转换呢?它会不会是一个突破口?

从Editable getText()源码看,它是在super.setText(text, BufferType.EDITABLE)中完成转换的。

在TextView源码中,setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)有这样一个流程分支:

private voidsetText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {

if(type == BufferType.EDITABLE || getKeyListener() != null|| needEditableForNotification) {

...

Editable t = mEditableFactory.newEditable(text);

text = t;

...

}

...

mBufferType = type;

setTextInternal(text);

...

}

由此可见,我们赋值给EditText的CharSequence对象先经过mEditableFactory转换为Editable对象,最终被真正赋值给EditText,mEditableFactory的类型正是Editable.Factory,这是一个静态内部类。

我们看看Editable.Factory的具体实现是什么:

/**

* Factory used by TextView to create new {@link Editable Editables}. You can subclass

* it to provide something other than {@link SpannableStringBuilder}.

*

* @see android.widget.TextView#setEditableFactory(Factory)

*/

public static class Factory {

private static Editable.Factory sInstance = newEditable.Factory();

/**

* Returns the standard Editable Factory.

*/

public static Editable.Factory getInstance() {

returnsInstance;

}

/**

* Returns a new SpannedStringBuilder from the specified

* CharSequence.  You can override this to provide

* a different kind of Spanned.

*/

public Editable newEditable(CharSequence source) {

return new SpannableStringBuilder(source);

}

}

很简单的转换,它将CharSequence对象转换为Editable的子类SpannableStringBuilder的对象。

我们看一看这个构造器:

public SpannableStringBuilder(CharSequence text, intstart, intend) {

...

mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));

...

if(text instanceofSpanned) {

Spanned sp = (Spanned) text;

Object[] spans = sp.getSpans(start, end, Object.class);

for(intii = 0; ii < spans.length; ii++) {

if(spans[ii] instanceofNoCopySpan) {

continue;

}

...

setSpan(false, spans[ii], st, en, fl, false);

}

restoreInvariants();

}

}

这就是轴承断掉的原因所在。

前面提到SpanWatcher继承自NoCopySpan,而NoCopySpan是一个标记接口。它的作用就是标记一个Span无法被拷贝。SpannableStringBuilder在构造的时候,会忽略掉所有NoCopySpan及其子类。因此,SelectionSpanWatcher没有被赋值给EditText的文本。

既然NoCopySpan不被复制,那我们等SpannableStringBuilder构造好后重新设置便好了。Editable.Factory的注释让我看到了希望。他可以被重写,并被重新注入EditText。

android.widget.TextView#setEditableFactory(Factory)

下面是重写的Editable.Factory,作用是重新把NoCopySpan设置到SpannableStringBuilder上:

package com.iyao

import android.text.Editable

import android.text.NoCopySpan

import android.text.SpannableStringBuilder

import android.text.Spanned

import android.text.style.BackgroundColorSpan

class NoCopySpanEditableFactory(private var arg val spans: NoCopySpan): Editable.Factory() {

override fun newEditable(source: CharSequence): Editable {

return SpannableStringBuilder.valueOf(source).apply {

spans.forEach {

setSpan(it, 0, source.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)

}

}

}

}

没错,算空行一共17行代码。它就是这个轮子的新轴承。现在我们重新使用它。

通过editText.setEditableFactory()换上新的轴承,让轮子跑起来:

editText.setEditableFactory(NoCopySpanEditableFactory(SelectionSpanWatcher(DataBindingSpan::class)))

editText.setOnKeyListener { v, keyCode, event ->

if(keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN) {

return @setOnKeyListenerKeyCodeDeleteHelper.onDelDown((v as EditText).text)

}

return @setOnKeyListenerfalse

}

一个“优雅的”实现诞生了,你可以像微博一样在评论中使用@人了。

运行效果:

 

5、微信的做法

微信的处理方式要简单一些,他们不禁止在Span覆盖的文本中插入光标,而是当Span覆盖的文本改变后清除Span以及数据。他们同样要监听删除键实现Span整体删除,只是表现上与微博稍有区别。

微信的三部曲。

首先,定义一个接口用来判断Span是否失效:

package com.iyao

import android.text.Spannable

interface RemoveOnDirtySpan {

fun isDirty(text: Spannable): Boolean

}

其次,让SpannableData实现此接口。当然,你也可以让RemoveOnDirtySpan继承DataBindingSpan,尽管我觉得这样不符合“六大”。

class SpannableData(privateval spanned: String): DataBindingSpan<String>, RemoveOnDirtySpan {

override fun spannedText(): CharSequence {

return SpannableString(spanned).apply {

setSpan(ForegroundColorSpan(Color.RED), 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

}

}

override fun bindingData(): String {

return spanned

}

override fun isDirty(text: Spannable): Boolean {

val spanStart = text.getSpanStart(this)

val spanEnd = text.getSpanEnd(this)

return spanStart >= 0&& spanEnd >= 0&& text.substring(spanStart, spanEnd) != spanned

}

}

最后,重新写一个DirtySpanWatcher用来删除失效的Span:

package com.iyao

import android.text.SpanWatcher

import android.text.Spannable

class DirtySpanWatcher(private val removePredicate: (Any) -> Boolean) : SpanWatcher {

override fun onSpanChanged(text: Spannable, what: Any, ostart: Int, oend: Int, nstart: Int,

nend: Int) {

if(what is RemoveOnDirtySpan && what.isDirty(text)) {

val spanStart = text.getSpanStart(what)

val spanEnd = text.getSpanEnd(what)

text.getSpans(spanStart, spanEnd, Any::class.java).filter {

removePredicate.invoke(it)

}.forEach {

text.removeSpan(it)

}

}

}

override fun onSpanRemoved(text: Spannable, what: Any, start: Int, end: Int) {

}

override fun onSpanAdded(text: Spannable, what: Any, start: Int, end: Int) {

}

}

现在,我们让微信也跑起来:

editText.setEditableFactory(NoCopySpanEditableFactory(DirtySpanWatcher{

it is ForegroundColorSpan || it is RemoveOnDirtySpan

}))

editText.setOnKeyListener { v, keyCode, event ->

if(keyCode == KeyEvent.KEYCODE_DEL && event.action == KeyEvent.ACTION_DOWN) {

KeyCodeDeleteHelper.onDelDown((v as EditText).text)

}

return @setOnKeyListenerfalse

}

需要注意,微信和微博有一点小区别,微博有二次确认删除选中,微信没有。代码上的差别仅仅是微信少了一个return@setOnKeyListener。

运行效果:

 

6、QQ的做法

QQ的做法太简单,我不太想讲它。这里写一个简单的Demo演示一下。

QQ同样需要用到DataBindingSpan<T>,甚至你也可以不用。它的核心是ImageSpan:

class SpannableData(privateval spanned: String): DataBindingSpan<String> {

override fun spannedText(): CharSequence {

returnSpannableString("@$spanned ").apply {

setSpan(ImageSpan(LabelDrawable("@$spanned", color = Color.LTGRAY), spanned), 0, length-1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

}

}

override fun bindingData(): String {

return spanned

}

}

现在只需要实现一个绘制文字的Drawable,这里我取名叫LabelDrawable,也许并不准确:

class LabelDrawable(val text: CharSequence, private val textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {

textSize = 42f

this.color = Color.DKGRAY

textAlign = Paint.Align.CENTER

}, color: Int): ColorDrawable(color) {

init {

calculateBounds()

}

override fun draw(canvas: Canvas) {

super.draw(canvas)

canvas.drawText(text, 0, text.length, bounds.centerX().toFloat(), bounds.centerY().toFloat() + getBaselineOffset(textPaint.fontMetrics), textPaint)

}

private fun calculateBounds() {

textPaint.getTextBounds(text.toString(), 0, text.length, bounds)

bounds.inset(-8, -4)

bounds.offset(8, 0)

}

private fun getBaselineOffset(fontMetrics: Paint.FontMetrics): Float {

return (fontMetrics.descent - fontMetrics.ascent) / 2- fontMetrics.descent

}

}

就像普通的Span一样使用他就行了。

运行效果:

 

如果想要做的更好一点,你需要处理多行文本measure、layout、draw等问题。给个小提示,TextView截屏也是一个Drawable。如果有一个View,即使它并未attach到Window上,我们也可以手动调用measure()、layout()、draw()方法获取一个View的截图Drawable用来添加到ImageSpan中使用,不过这样无法响应触摸事件。

7、获取文本中绑定的数据

用下面的代码就行了:

val strings = editText.text.let {

it.getSpans(0, it.length, DataBindingSpan::class.java)

}.map { it.bindingData() }

8、本文源码附件下载

(因无法上传附件,请从链接:http://www.52im.net/thread-2165-1-1.html 处下载之!)

9、题外话:本文代码是Kotlin写的,但我想要Java版的@人实现,怎么办?

是的,Kotlin暂时还没这么广泛的使用,用不了。

但,@这个看似很简都的功能,实际上要不出bug的做好,还是有点难度,或者说代码量还不算小。

那么,哪里能找到靠谱的@人功能的Java版实现?

答案在这里:可以下载网易云信官方开源的IM Demo,里面就有@功能完整代码实现:

 

▲ @人功能完整源码位置

别跟我说这是违法的,他们自已说是开源。。。

网易云信的IM Demo下载地址:点此进入

网易云信的IM Demo的Github地址:https://github.com/netease-im/NIM_Android_Demo

好了,我没有收网易云信任何好处费,之所以推荐你去“扒”它的源码,是因为我评估了主流的第3方IM开源的Demo代码后,@人功能写的还算不错的,就只有网易云信了,木有办法。

附录:更多精品资源下载

[1] 精品源码下载:

Java NIO基础视频教程、MINA视频教程、Netty快速入门视频 [有源码]

轻量级即时通讯框架MobileIMSDK的iOS源码(开源版)[附件下载]

开源IM工程“蘑菇街TeamTalk”2015年5月前未删减版完整代码 [附件下载]

微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]

NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示 [附件下载]

NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示 [附件下载]

用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]

高仿Android版手机QQ可拖拽未读数小气泡源码 [附件下载]

一个WebSocket实时聊天室Demo:基于node.js+socket.io [附件下载]

Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]

高仿Android版手机QQ首页侧滑菜单源码 [附件下载]

开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]

分享java AMR音频文件合并源码,全网最全

微信团队原创Android资源混淆工具:AndResGuard [有源码]

一个基于MQTT通信协议的完整Android推送Demo [附件下载]

Android版高仿微信聊天界面源码 [附件下载]

高仿手机QQ的Android版锁屏聊天消息提醒功能 [附件下载]

高仿iOS版手机QQ录音及振幅动画完整实现 [源码下载]

Android端社交应用中的评论和回复功能实战分享[图文+源码]

Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展[图文+源码]

[2] 精品文档和工具下载:

计算机网络通讯协议关系图(中文珍藏版)[附件下载]

史上最全即时通讯软件简史(精编大图版)[附件下载]

重磅发布:《阿里巴巴Android开发手册(规约)》[附件下载]

阿里技术结晶:《阿里巴巴Java开发手册(规约)-终极版》[附件下载]

基于RTMP协议的流媒体技术的原理与应用(技术论文)[附件下载]

独家发布《TCP/IP详解 卷1:协议》CHM版 [附件下载]

良心分享:WebRTC 零基础开发者教程(中文)[附件下载]

MQTT协议手册(中文翻译版)[附件下载]

经典书籍《UNIX网络编程》最全下载(卷1+卷2、中文版+英文版)[附件下载]

音视频开发理论入门书籍之《视频技术手册(第5版)》[附件下载]

国际电联H.264视频编码标准官方技术手册(中文版)[附件下载]

Apache MINA2.0 开发指南(中文版)[附件下载]

网络通讯数据抓包和分析工具 Wireshark 使用教程(中文) [附件下载]

最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]

高性能网络编程经典:《The C10K problem(英文)》[附件下载]

即时通讯系统的原理、技术和应用(技术论文)[附件下载]

技术论文:微信对网络影响的技术试验及分析[附件下载]

华为内部3G网络资料: WCDMA系统原理培训手册[附件下载]

网络测试:Android版多路ping命令工具EnterprisePing[附件下载]

Android反编译利器APKDB:没有美工的日子里继续坚强的撸

一款用于P2P开发的NAT类型检测工具 [附件下载]

两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]

[3] 精选视频、演讲PPT下载:

美图海量用户的IM架构零基础演进之路(PPT)[附件下载]

开源实时音视频工程WebRTC的架构详解与实践总结(PPT+视频)[附件下载]

QQ空间百亿级流量的社交广告系统架构实践(视频+PPT)[附件下载]

海量实时消息的视频直播系统架构演进之路(视频+PPT)[附件下载]

YY直播在移动弱网环境下的深度优化实践分享(视频+PPT)[附件下载]

QQ空间移动端10亿级视频播放技术优化揭秘(视频+PPT)[附件下载]

RTC实时互联网2017年度大会精选演讲PPT [附件下载]

微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]

微服务理念在微信海量用户后台架构中的实践(视频+PPT)[附件下载]

移动端IM开发和构建中的技术难点实践分享(视频+PPT)[附件下载]

网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]

腾讯音视频实验室:直面音视频质量评估之痛(视频+PPT)[附件下载]

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT[附件下载]

微信朋友圈海量技术之道PPT[附件下载]

手机淘宝消息推送系统的架构与实践(音频+PPT)[附件下载]

如何进行实时音视频的质量评估与监控(视频+PPT)[附件下载]

Go语言构建高并发消息推送系统实践PPT(来自360公司)[附件下载]

网易IM云千万级并发消息处理能力的架构设计与实践PPT [附件下载]

手机QQ的海量用户移动化实践分享(视频+PPT)[附件下载]

钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)[附件下载]

微信技术总监谈架构:微信之道——大道至简(PPT讲稿)[附件下载]

Netty的架构剖析及应用案例介绍(视频+PPT)[附件下载]

声网架构师谈实时音视频云的实现难点(视频采访)

滴滴打车架构演变及应用实践(PPT讲稿)[附件下载]

微信海量用户背后的后台系统存储架构(视频+PPT)[附件下载]

在线音视频直播室服务端架构最佳实践(视频+PPT)[附件下载]

从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT)[附件下载]

微信移动端应对弱网络情况的探索和实践PPT[附件下载]

Android版微信从300KB到30MB的技术演进(PPT讲稿)[附件下载]

从零开始搭建瓜子二手车IM系统(PPT)[附件下载]

极光分享:高并发海量消息推送系统架构演进(视频+PPT)[附件下载]

(本文同步发布于:http://www.52im.net/thread-2165-1-1.html

Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展的更多相关文章

  1. 【输入法】向Android端Gboard字典中导入PC端搜狗细胞词库

    [输入法]向Android端Gboard字典中导入PC端搜狗细胞词库 环境 Android 5.1.1 Gboard 8.7.10.272217667-release -armeabi-v7a PC端 ...

  2. h5聊天室web端(仿微博、微信)|h5仿微信网页端|仿微信界面弹窗

    这段时间一直在着手h5开发手机端聊天系统——html5仿微信聊天室,最近又在原先基础上开发了一个仿微信.微博网页web版聊天系统,使用到了HTML5+css3+jQuery+wcpop等技术开发,弹窗 ...

  3. 我的Android进阶之旅------>Android实现用Android手机控制PC端的关机和重启的功能(三)Android客户端功能实现

    我的Android进阶之旅------>Android实现用Android手机控制PC端的关机和重启的功能(一)PC服务器端(地址:http://blog.csdn.net/ouyang_pen ...

  4. 在XMPP的JAVA开源实现Openfire中,增加LBS 附近的人功能

    1. XMPP协议 与 Openfire XMPP协议是IM领域的标准协议了,具体可参考  http://xmpp.org   及RFC6120,RFC6121,RFC6122等相关文档. http: ...

  5. Android端上传图片到后台,存储到数据库中 详细代码

    首先点击头像弹出popwindow,点击相册,相机,调用手机自带的裁剪功能,然后异步任务类访问服务器,上传头像,保存到数据库中, 下面写出popwindow的代码 //设置popwindow publ ...

  6. 5分钟实现Android中更换头像功能

    写在前面:更换头像这个功能在用户界面几乎是100%出现的.通过拍摄照片或者调用图库中的图片,并且进行剪裁,来进行头像的设置.功能相关截图如下: 下面我们直接看看完整代码吧: 1 2 3 4 5 6 7 ...

  7. 关于基于Linphone的视频通话Android端开发过程中遇到的问题

    关于基于Linphone的视频通话Android端开发过程中遇到的问题 运用开源项目Linphone的SDK进行开发,由于是小组进行开发,我主要负责的是界面部分. 由于当时是初学Android开发,对 ...

  8. android asmack 注册 登陆 聊天 多人聊天室 文件传输

    XMPP协议简介 XMPP协议(Extensible Messaging and PresenceProtocol,可扩展消息处理现场协议)是一种基于XML的协议,目的是为了解决及时通信标准而提出来的 ...

  9. React Native(十五)——RN中的分享功能

    终于,终于,可以总结自己使用RN时的分享功能了-- 为什么呢?且听我慢慢道来吧: 从刚开始接触React Native(2017年9月中旬)就着手于分享功能,直到自己参与公司的rn项目开发中,再到现在 ...

随机推荐

  1. adi i2s 提供的axi_lite接口说明

    总共定义了4个寄存器,位宽32位,也就是 偏移地址*4以下是PS写数据when 0 => I2S_RESET_REG <= wr_data; when 1 => I2S_CONTRO ...

  2. cocos creator 碰撞检测

    creator的碰撞检测系统分为碰撞检测系统和物理碰撞检测系统两个模块,并且这两个模块是相互独立的(这边主要是非物理碰撞检测系统) 1.在制作碰撞检测系统的时候要对物体进行分组,即指定节点的分组与分组 ...

  3. jdk7_ConcurrentHashMap 图示

    jdk7_ConcurrentHashMap初始化图示 jdk7_ConcurrentHashMap_put和get操作_扩容_线程安全的分析

  4. Docker commit 命令保存的镜像文件太大的问题

    基础镜像: centos7.5 进入容器后, 先后安装了 jdk1.8, maven3.6.0, git, rocketmq4.3.2 安装完成后使用 docker commit 命令保存为镜像 结果 ...

  5. VS2017打包注册IE插件及修改IE安全选项设置

    前言 最近项目需要在浏览器环境下读取员工身份证信息,要实现网页与硬件设备通信,考虑了几种实现方式: 1.借助ActiveX插件,通过程序库直接与设备通信. 优点:厂家提供了IE插件,开发简单 缺点:只 ...

  6. C#实现视频监控客户端onvif协议一

    前言 最近做的项目是监控方面的,需要对接各种摄像头,之前的方案是把各个厂家的SDK都集成到系统中,然后让用户进行切换,后来知道了Onvif (自行百度具体概念)这个东西.原来早就有人一统江湖了. on ...

  7. apache与nginx原理

    参考:  https://blog.csdn.net/yf3585595511/article/details/54933646

  8. net core EF 链接mysql 数据库

    这个主要是一个demo.就在一个工程里面写的 安装MySql.Data.EntityFrameworkCore 增加DbContext 相当于程序与数据库的中间层 public class Ident ...

  9. linux安装vsftp服务

    如果管理一个网站,需要经常上传下载一些文件,通过scp传输吗?当然不是,太麻烦了,而且首先你需要本机是linux的系统,这时我们需要一个工具,叫ftp. ftp是文件传输协议,通过它可以很方便上传下载 ...

  10. [Log函数]C++log函数使用

    先引入头文件#include<cmath> 以e为底:log(exp(n)) 以10为底:log10(n) 以m为底:log(n)/log(m)