默認
打賞 發表評論 3
Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]
閱讀(9350) | 評論(3 收藏1 淘帖1

本文作者水月沐風,感謝無私分享。


1、內容概述


在Android的日常開發中,尤其像IM這類社交應用中,評論與回復功能是我們經常遇到的需求之一(比如微信的朋友圈、手機QQ的QQ空間等),其中評論與回復列表的展示一般在功能模塊中占比較大。對于需求改動和迭代較頻繁的公司來說,如何快速開發一個二級界面來適應我們的功能需求無疑優先級更高一些。

首先我們來看看其他社交類app的評論與回復列表如何展示的:
Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]_1.png Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]_2.png Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]_111.jpg

第1張圖是我們設計給我找的,他說要按照這個風格來,盡量將評論和回復內容在一個頁面展示。好吧,沒辦法,畢竟我們做前端的,UI要看設計臉色,數據要看后臺臉色😂。

第2張圖Twitter不用說了,全球知名社交平臺,上億用戶量,他們的評論回復都只展示一級數據(評論數據),其他更多內容(回復內容),是需要頁面跳轉去查看,知乎也類似。

第3張圖微信的朋友圈大家最熟悉了,評論回復也同樣都只展示一級數據(評論數據),其他更多內容(回復內容),是需要頁面跳轉去查看,這個主流的社區APP(包括IM)都差不多。

看到設計圖,我們腦海肯定第一時間聯想一下解決方案:
用recyclerview?listview?不對,分析一下它的層級發現,評論是一個列表,里面的回復又是一個列表,難道用recyclerview或者listview的嵌套?

抱著不確定的態度,立馬去網上查一下,果不其然,搜到的實現方式大多都是用嵌套實現的,來公司之前,其中一個項目里的評論回復功能就是用的嵌套listview,雖然處理了滑動沖突問題,但效果不佳,而且時常卡頓,所以,這里我肯定要換個思路。

網上還有說用自定義view實現的,但我發現大多沒有處理view的復用,而且開發成本大,暫時不予考慮。那怎么辦?無意中看到expandable這個關鍵詞,我突然想到谷歌很早之前出過一個擴展列表的控件 - ExpandableListView,但聽說比較老,存在一些問題。算了,試試再說,順便熟悉一下以前基礎控件的用法。

先來看一下最終的效果圖吧:
Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]_3.gif

這只是一個簡單的效果圖,你可以在此基礎上來完善它。好了,廢話不多說,下面讓我們來看看效果具體如何實現的吧。

大家應該不難看出來,頁面整體采用了CoordinatorLayout來實現詳情頁的頂部視差效。同時,這里我采用ExpandableListView來實現多級列表,然后再解決它們的嵌套滑動問題。OK,我們先從ExpandableListView開始動手。

2、相關例子


用于IM中圖片壓縮的Android工具類源碼,效果可媲美微信 [附件下載]
高仿Android版手機QQ可拖拽未讀數小氣泡源碼 [附件下載]
Android聊天界面源碼:實現了聊天氣泡、表情圖標(可翻頁) [附件下載]
高仿Android版手機QQ首頁側滑菜單源碼 [附件下載]
分享java AMR音頻文件合并源碼,全網最全
Android版高仿微信聊天界面源碼 [附件下載]
高仿手機QQ的Android版鎖屏聊天消息提醒功能 [附件下載]
高仿iOS版手機QQ錄音及振幅動畫完整實現 [源碼下載]

3、ExpandableListView


官方對于ExpandableListView給出這樣的解釋:

A view that shows items in a vertically scrolling two-level list. This differs from the ListView by allowing two levels: groups which can individually be expanded to show its children. The items come from the ExpandableListAdapter associated with this view.


簡單來說,ExpandableListView是一個用于垂直方向滾動的二級列表視圖,ExpandableListView與listview不同之處在于,它可以實現二級分組,并通過ExpandableListAdapter來綁定數據和視圖。下面我們來一起實現上圖的效果。

3.1布局中定義


首先,我們需要在xml的布局文件中聲明ExpandableListView:
<ExpandableListView
    android:id="@+id/detail_page_lv_comment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:divider="@null"
    android:layout_marginBottom="64dp"
    android:listSelector="@android:color/transparent"
    android:scrollbars="none"/>

這里需要說明兩個問題:
  • 1)ExpandableListView默認為它的item加上了點擊效果,由于item里面還包含了childItem,所以,點擊后,整個item里面的內容都會有點擊效果。我們可以取消其點擊特效,避免其影響用戶體驗,只需要設置如上代碼中的listSelector即可;
  • 2)ExpandableListView具有默認的分割線,可以通過divider屬性將其隱藏。


3.2設置Adapter


正如使用listView那樣,我們需要為ExpandableListView設置一個適配器Adapter,為其綁定數據和視圖。

ExpandableListView的adapter需要繼承自ExpandableListAdapter,具體代碼如下:
public class CommentExpandAdapter extends BaseExpandableListAdapter {
    private static final String TAG = "CommentExpandAdapter";
    private List<CommentDetailBean> commentBeanList;
    private Context context;
    public CommentExpandAdapter(Context context, List<CommentDetailBean> commentBeanList)               {
        this.context = context;
        this.commentBeanList = commentBeanList;
    }
    @Override
    public int getGroupCount() {
        return commentBeanList.size();
    }
    @Override
    public int getChildrenCount(int i) {
        if(commentBeanList.get(i).getReplyList() == null){
            return 0;
        }else {
            return commentBeanList.get(i).getReplyList().size()>0 ? commentBeanList.get(i).getReplyList().size():0;
        }
    }
    @Override
    public Object getGroup(int i) {
        return commentBeanList.get(i);
    }
    @Override
    public Object getChild(int i, int i1) {
        return commentBeanList.get(i).getReplyList().get(i1);
    }
    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }
    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return getCombinedChildId(groupPosition, childPosition);
    }
    @Override
    public boolean hasStableIds() {
        return true;
    }
    boolean isLike = false;
    @Override
    public View getGroupView(final int groupPosition, boolean isExpand, View convertView, ViewGroup viewGroup) {
        final GroupHolder groupHolder;
        if(convertView == null){
            convertView = LayoutInflater.from(context).inflate(R.layout.comment_item_layout, viewGroup, false);
            groupHolder = new GroupHolder(convertView);
            convertView.setTag(groupHolder);
        }else {
            groupHolder = (GroupHolder) convertView.getTag();
        }
        Glide.with(context).load(R.drawable.user_other)
                .diskCacheStrategy(DiskCacheStrategy.RESULT)
                .error(R.mipmap.ic_launcher)
                .centerCrop()
                .into(groupHolder.logo);
        groupHolder.tv_name.setText(commentBeanList.get(groupPosition).getNickName());
        groupHolder.tv_time.setText(commentBeanList.get(groupPosition).getCreateDate());
        groupHolder.tv_content.setText(commentBeanList.get(groupPosition).getContent());
        groupHolder.iv_like.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(isLike){
                    isLike = false;
                    groupHolder.iv_like.setColorFilter(Color.parseColor("#aaaaaa"));
                }else {
                    isLike = true;
                    groupHolder.iv_like.setColorFilter(Color.parseColor("#FF5C5C"));
                }
            }
        });
        return convertView;
    }
    @Override
    public View getChildView(final int groupPosition, int childPosition, boolean b, View convertView, ViewGroup viewGroup) {
        final ChildHolder childHolder;
        if(convertView == null){
            convertView = LayoutInflater.from(context).inflate(R.layout.comment_reply_item_layout,viewGroup, false);
            childHolder = new ChildHolder(convertView);
            convertView.setTag(childHolder);
        }
        else {
            childHolder = (ChildHolder) convertView.getTag();
        }
        String replyUser = commentBeanList.get(groupPosition).getReplyList().get(childPosition).getNickName();
        if(!TextUtils.isEmpty(replyUser)){
            childHolder.tv_name.setText(replyUser + ":");
        }
        childHolder.tv_content.setText(commentBeanList.get(groupPosition).getReplyList().get(childPosition).getContent());
        return convertView;
    }
    @Override
    public boolean isChildSelectable(int i, int i1) {
        return true;
    }
    private class GroupHolder{
        private CircleImageView logo;
        private TextView tv_name, tv_content, tv_time;
        private ImageView iv_like;
        public GroupHolder(View view) {
            logo =  view.findViewById(R.id.comment_item_logo);
            tv_content = view.findViewById(R.id.comment_item_content);
            tv_name = view.findViewById(R.id.comment_item_userName);
            tv_time = view.findViewById(R.id.comment_item_time);
            iv_like = view.findViewById(R.id.comment_item_like);
        }
    }
    private class ChildHolder{
        private TextView tv_name, tv_content;
        public ChildHolder(View view) {
            tv_name = (TextView) view.findViewById(R.id.reply_item_user);
            tv_content = (TextView) view.findViewById(R.id.reply_item_content);
        }
    }
}

一般情況下,我們自定義自己的ExpandableListAdapter后,需要實現以下幾個方法:

  • 構造方法,這個應該無需多說了,一般用來初始化數據等操作。
  • getGroupCount,返回group分組的數量,在當前需求中指代評論的數量。
  • getChildrenCount,返回所在group中child的數量,這里指代當前評論對應的回復數目。
  • getGroup,返回group的實際數據,這里指的是當前評論數據。
  • getChild,返回group中某個child的實際數據,這里指的是當前評論的某個回復數據。
  • getGroupId,返回分組的id,一般將當前group的位置傳給它。
  • getChildId,返回分組中某個child的id,一般也將child當前位置傳給它,不過為了避免重復,可以使用getCombinedChildId(groupPosition, childPosition);來獲取id并返回。
  • hasStableIds,表示分組和子選項是否持有穩定的id,這里返回true即可。
  • isChildSelectable,表示分組中的child是否可以選中,這里返回true。
  • getGroupView,即返回group的視圖,一般在這里進行一些數據和視圖綁定的工作,一般為了復用和高效,可以自定義ViewHolder,用法與listview一樣,這里就不多說了。
  • getChildView,返回分組中child子項的視圖,比較容易理解,第一個參數是當前group所在的位置,第二個參數是當前child所在位置。

這里的數據是我自己做的模擬數據,不過應該算是較為通用的格式了,大體格式如下:
Android端社交應用中的評論和回復功能實戰分享[圖文+源碼]_4.png
插一嘴:歡迎使用 http://www.wanandroid.com/tools/mockapi 模擬數據。

一般情況下,我們后臺會通過接口返回給我們一部分數據,如果想要查看更多評論,需要跳轉到“更多頁面”去查看,這里為了方便,我們只考慮加載部分數據。

3.3Activity中使用


接下來,我們就需要在activity中顯示評論和回復的二級列表了:
private ExpandableListView expandableListView;
private CommentExpandAdapter adapter;
private CommentBean commentBean;
private List<CommentDetailBean> commentsList;
...
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
    private void initView() {
        expandableListView = findViewById(R.id.detail_page_lv_comment);
        initExpandableListView(commentsList);
    }
    /**
     * 初始化評論和回復列表
     */
    private void initExpandableListView(final List<CommentDetailBean> commentList){
        expandableListView.setGroupIndicator(null);
        //默認展開所有回復
        adapter = new CommentExpandAdapter(this, commentList);
        expandableListView.setAdapter(adapter);
        for(int i = 0; i<commentList.size(); i++){
            expandableListView.expandGroup(i);
        }
        expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
            @Override
            public boolean onGroupClick(ExpandableListView expandableListView, View view, int groupPosition, long l) {
                boolean isExpanded = expandableListView.isGroupExpanded(groupPosition);
                Log.e(TAG, "onGroupClick: 當前的評論id>>>"+commentList.get(groupPosition).getId());
//                if(isExpanded){
//                    expandableListView.collapseGroup(groupPosition);
//                }else {
//                    expandableListView.expandGroup(groupPosition, true);
//                }
                return true;
            }
        });
        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long l) {
                Toast.makeText(MainActivity.this,"點擊了回復",Toast.LENGTH_SHORT).show();
                return false;
            }
        });
        expandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
            @Override
            public void onGroupExpand(int groupPosition) {
                //toast("展開第"+groupPosition+"個分組");
            }
        });
    }
    /**
     * func:生成測試數據
     * @return 評論數據
     */
    private List<CommentDetailBean> generateTestData(){
        Gson gson = new Gson();
        commentBean = gson.fromJson(testJson, CommentBean.class);
        List<CommentDetailBean> commentList = commentBean.getData().getList();
        return commentList;
    }

就以上代碼作一下簡單說明:

  • 【1】ExpandableListView在默認情況下會為我們自帶分組的icon(▶️),當前需求下,我們根本不需要展示,可以通過expandableListView.setGroupIndicator(null)來隱藏;
  • 【2】一般情況下,我們可能需要默認展開所有的分組,我就可以通過循環來調用expandableListView.expandGroup(i);方法;
  • 【3】ExpandableListView為我們提供了group和child的點擊事件,分別通過setOnGroupClickListener和setOnChildClickListener來設置。值得注意的是,group的點擊事件里如果我們返回的是false,那么我們點擊group就會自動展開,但我這里碰到一個問題,當我返回false時,第一條評論數據會多出一條。通過百度查找方法,雖然很多類似問題,但終究沒有解決,最后我返回了ture,并通過以下代碼手動展開和收縮就可以了:
    if(isExpanded){
        expandableListView.collapseGroup(groupPosition);
    }else {
        expandableListView.expandGroup(groupPosition, true);
    }
  • 【4】此外,我們還可以通過setOnGroupExpandListener和setOnGroupCollapseListener來監聽ExpandableListView的分組展開和收縮的狀態。

4、評論和回復功能


為了模擬整個評論和回復功能,我們還需要手動插入收據并刷新數據列表。這里我就簡單做一下模擬,請忽略一些UI上的細節。

4.1插入評論數據


插入評論數據比較簡單,只需要在list中插入一條數據并刷新即可:
String commentContent = commentText.getText().toString().trim();
if(!TextUtils.isEmpty(commentContent)){
    //commentOnWork(commentContent);
    dialog.dismiss();
    CommentDetailBean detailBean = new CommentDetailBean("小明", commentContent,"剛剛");
    adapter.addTheCommentData(detailBean);
    Toast.makeText(MainActivity.this,"評論成功",Toast.LENGTH_SHORT).show();
}else {
    Toast.makeText(MainActivity.this,"評論內容不能為空",Toast.LENGTH_SHORT).show();
}

adapter中的addTheCommentData方法如下:
public void addTheCommentData(CommentDetailBean commentDetailBean){
    if(commentDetailBean!=null){
        commentBeanList.add(commentDetailBean);
        notifyDataSetChanged();
    }else {
        throw new IllegalArgumentException("評論數據為空!");
    }
}

代碼比較容易理解,就不多做說明了。

4.2插入回復數據


首先,我們需要實現點擊某一條評論,然后@ta,那么我們需要在group的點擊事件里彈起回復框:
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
        @Override
        public boolean onGroupClick(
            ExpandableListView expandableListView, View view, int groupPosition, long l) {
            showReplyDialog(groupPosition);
            return true;
        }
    });
......
/**
 * func:彈出回復框
 */
private void showReplyDialog(final int position){
    dialog = new BottomSheetDialog(this);
    View commentView = LayoutInflater.from(this).inflate(R.layout.comment_dialog_layout,null);
    final EditText commentText = (EditText) commentView.findViewById(R.id.dialog_comment_et);
    final Button bt_comment = (Button) commentView.findViewById(R.id.dialog_comment_bt);
    commentText.setHint("回復 " + commentsList.get(position).getNickName() + " 的評論:");
    dialog.setContentView(commentView);
    bt_comment.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String replyContent = commentText.getText().toString().trim();
            if(!TextUtils.isEmpty(replyContent)){
                dialog.dismiss();
                ReplyDetailBean detailBean = new ReplyDetailBean("小紅",replyContent);
                adapter.addTheReplyData(detailBean, position);
                Toast.makeText(MainActivity.this,"回復成功",Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(MainActivity.this,"回復內容不能為空",Toast.LENGTH_SHORT).show();
            }
        }
    });
    commentText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }
        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            if(!TextUtils.isEmpty(charSequence) && charSequence.length()>2){
                bt_comment.setBackgroundColor(Color.parseColor("#FFB568"));
            }else {
                bt_comment.setBackgroundColor(Color.parseColor("#D8D8D8"));
            }
        }
        @Override
        public void afterTextChanged(Editable editable) {
        }
    });
    dialog.show();
}

插入回復的數據與上面插入評論類似,這里貼一下adapter中的代碼:
/**
 * by moos on 2018/04/20
 * func:回復成功后插入一條數據
 * @param replyDetailBean 新的回復數據
 */
public void addTheReplyData(ReplyDetailBean replyDetailBean, int groupPosition){
    if(replyDetailBean!=null){
        Log.e(TAG, "addTheReplyData: >>>>該刷新回復列表了:"+replyDetailBean.toString() );
        if(commentBeanList.get(groupPosition).getReplyList() != null ){
            commentBeanList.get(groupPosition).getReplyList().add(replyDetailBean);
        }else {
            List<ReplyDetailBean> replyList = new ArrayList<>();
            replyList.add(replyDetailBean);
            commentBeanList.get(groupPosition).setReplyList(replyList);
        }
        notifyDataSetChanged();
    }else {
        throw new IllegalArgumentException("回復數據為空!");
    }
}

需要注意一點:由于不一定所有的評論都有回復數據,所以在插入數據前我們要判斷ReplyList是否為空,如果不為空,直接獲取當前評論的回復列表,并插入數據;如果為空,需要new一個ReplyList,插入數據后還要為評論set一下ReplyList。

5、解決CoordinatorLayout與ExpandableListView嵌套問題


如果你不需要使用CoordinatorLayout或者NestedScrollView,可以跳過本小節

一般情況下,我們產品為了更好的用戶體驗,還需要我們加上類似的頂部視差效果或者下拉刷新等,這就要我們處理一些常見的嵌套滑動問題了。

由于CoordinatorLayout實現NestedScrollingParent接口,RecycleView實現了NestedScrollingChild接口,所以就可以在NestedScrollingChildHelper的幫助下實現嵌套滑動。

那么我們也可以通過自定義的ExpandableListView實現NestedScrollingChild接口來達到同樣的效果:
/**
 * Desc: 自定義ExpandableListView,解決與CoordinatorLayout滑動沖突問題
 */
public class CommentExpandableListView extends ExpandableListView implements NestedScrollingChild{
    private NestedScrollingChildHelper mScrollingChildHelper;
    public CommentExpandableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScrollingChildHelper = new NestedScrollingChildHelper(this);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setNestedScrollingEnabled(true);
        }
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }
    @Override
    public boolean isNestedScrollingEnabled() {
        return mScrollingChildHelper.isNestedScrollingEnabled();
    }
    @Override
    public boolean startNestedScroll(int axes) {
        return mScrollingChildHelper.startNestedScroll(axes);
    }
    @Override
    public void stopNestedScroll() {
        mScrollingChildHelper.stopNestedScroll();
    }
    @Override
    public boolean hasNestedScrollingParent() {
        return mScrollingChildHelper.hasNestedScrollingParent();
    }
    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
        return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }
    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }
    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
}

代碼就不解釋了,畢竟這不是本篇文章等重點,大家可以去網上查閱NestedScrollView相關文章或者源碼去對照理解。

NestedScroll這塊還可以學習:
Android NestedScrolling機制完全解析:帶你玩轉嵌套滑動
一點見解:Android嵌套滑動和NestedScrollView

6、源碼附件下載


CommentWithReplyView-master-master(52im.net).zip (350.51 KB , 下載次數: 6 , 售價: 1 金幣)

(原文鏈接:https://www.jianshu.com/p/eda8d09c9d7a,有改動)

附錄:全站精品資源下載


[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端社交應用中的評論和回復功能實戰分享[圖文+源碼]
仿微信的IM聊天時間顯示格式(含iOS/Android/Web實現)[圖文+源碼]

[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下載:
開源實時音視頻工程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講稿)[附件下載]

即時通訊網 - 即時通訊開發者社區! 來源: - 即時通訊開發者社區!

上一篇:開源實時音視頻工程WebRTC的架構詳解與實踐總結(PPT+視頻)[附件下載]下一篇:美圖海量用戶的IM架構零基礎演進之路(PPT) [附件下載]

本帖已收錄至以下技術專輯

推薦方案
評論 3
頂,學習一下
頂,學習一下
剛入門,來學習學習
打賞樓主 ×
使用微信打賞! 使用支付寶打賞!

返回頂部
曾氏料二肖中特