1. 程式人生 > >Android端IM應用中的@人功能實現:仿微博、QQ、微信,零入侵、高可擴充套件

Android端IM應用中的@人功能實現:仿微博、QQ、微信,零入侵、高可擴充套件

本文由“貓爸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

}

需要注意,微信和微博有一點小區別,微博有二次確認刪除選中,微信沒有。程式碼上的差別僅僅是微信少了一個[email protected]

執行效果:

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