Android 騰訊互動直播集成

最近剛用完互動直播sdk 做完直播,簡單記錄下。

官網(wǎng):https://cloud.tencent.com/product/ilvb

隨心博 demo:https://cloud.tencent.com/document/product/268/7658

直播接口:https://cloud.tencent.com/document/product/268/7685

直播API: https://zhaoyang21cn.github.io/iLiveSDK_Help/android_help/

一.旁路推流

首先先弄懂“旁路”的概念

凡是通過url觀看的直播都是旁路直播,旁路直播要進(jìn)行旁路推流,否則旁路直播無法觀看直播。

向在app內(nèi)我們是通過joinRoom() 加入的房間,所以不屬于旁路直播。

二.主播方

大概流程是這個樣子,根據(jù)自己需求進(jìn)行變更

1.向服務(wù)器請求房間號。

2.根據(jù)房間號,創(chuàng)建房間。

 public void createRoom() {
        ILVLiveRoomOption hostOption = new ILVLiveRoomOption(”主播ID“)
                .roomDisconnectListener(this)
                .autoCamera(true)
                .videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
                .controlRole(CurLiveInfo.getCurRole())
                //角色是主播 一定不能設(shè)置錯  主播走核心機房  觀眾走邊緣機房
//                .controlRole("LiveMaster")
                .videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO);
     
        int ret = ILVLiveManager.getInstance().createRoom(“房間ID”, hostOption, new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
            
               if (getView()!=null)
                   getView().enterRoomComplete(MySelfInfo.getInstance().getIdStatus(), true);
                ILiveLog.d(TAG, "ILVB-SXB|startEnterRoom->create room sucess");
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
            
               if (getView()!=null)
                   getView().errRoomFail();
                ILiveLog.d(TAG, "ILVB-SXB|createRoom->create room failed:" + module + "|" + errCode + "|" + errMsg);
            }
        });
  
    }

4.創(chuàng)建房間成功后有旁路直播開啟旁路推流。


public void pushStream() {
        /**
         * 旁路直播 退出房間時必須退出推流。否則會占用后臺channel。
         */
        ILivePushOption option = new ILivePushOption();
        option.encode(ILivePushOption.Encode.HLS);
//        option.setRecordFileType(ILivePushOption.RecordFileType.RECORD_HLS_FLV_MP4);
        option.setRecordFileType(ILivePushOption.RecordFileType.RECORD_HLS_FLV_MP4);
//        option.channelName("跟ios協(xié)商");
        //推流格式 到時候跟ios統(tǒng)一
        pushStream(option);


    }
/**
     * 開啟推流
     *
     * @param option
     */
    public void pushStream(ILivePushOption option) {
        if (!isPushed) {
            ILiveRoomManager.getInstance().startPushStream(option, new ILiveCallBack<ILivePushRes>() {
                @Override
                public void onSuccess(ILivePushRes data) {
//                List<ILivePushUrl> liveUrls = data.getUrls();
                    Log.i("pushStream", "推流成功?。。。。。?!");
                 
                }

                @Override
                public void onError(String module, int errCode, String errMsg) {
                    Log.i("pushStream", "推流失敗?。。。。。?!");
                }
            });
        }
    }

5.退出房間前:在房間發(fā)送自定義消息告訴觀眾主播要退出房間,方便觀眾端切換主播已離開界面,并停止推流。

發(fā)送自定義消息退出房間

 public void sendExitRoomMSG() {
        ILVCustomCmd cmd = new ILVCustomCmd();
        cmd.setCmd(Constans.AVIMCMD_EXITLIVE);
        cmd.setParam(sendExitRoomJson());
        cmd.setType(ILVText.ILVTextType.eGroupMsg);
        ILVLiveManager.getInstance().sendCustomCmd(cmd, new ILiveCallBack<TIMMessage>() {
            @Override
            public void onSuccess(TIMMessage data) {
             
               if (isPushed) {
                    stopStream();
                }
                callExitRoom();

            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
            }

        });
    }

停止推流

 /**
     * 退出直播的時候關(guān)閉推流
     */
    public void stopStream() {
        ILiveRoomManager.getInstance().stopPushStream(streamChannelID, new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                SxbLog.e(TAG, "stopPush->success");
                isPushed = false;
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
                SxbLog.e(TAG, "stopPush->failed:" + module + "|" + errCode + "|" + errMsg);
            }
        });

    }

6.退出房間。

private void callExitRoom() {
        ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
        quitLiveRoom();
    }

三.觀眾方

大概流程是這個樣子,根據(jù)自己需求進(jìn)行變更。

1.向服務(wù)器請求房間號。

2.根據(jù)房間號加入房間。

 //加入房間
    public void joinRoom(int roomId) {
        ILVLiveManager.getInstance().quitRoom(null);
        this.rommId = roomId + "";
        ILVLiveRoomOption memberOption = new ILVLiveRoomOption(ConfigUtils.getUid())
                .autoCamera(false)
                .roomDisconnectListener(this)
                .videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
                .controlRole("Guest")
                .authBits(AVRoomMulti.AUTH_BITS_JOIN_ROOM | AVRoomMulti.AUTH_BITS_RECV_AUDIO | AVRoomMulti.AUTH_BITS_RECV_CAMERA_VIDEO | AVRoomMulti.AUTH_BITS_RECV_SCREEN_VIDEO)
                .videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO)
                .autoMic(false);
        int ret = ILVLiveManager.getInstance().joinRoom(roomId, memberOption, new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                ILiveLog.d(TAG, "ILVB-Suixinbo|startEnterRoom->join room sucess");
                if (null != mGuestLiveView)

                    mGuestLiveView.enterRoomComplete(true);
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
                LogUtils.d("suyan", "===========加入房間失敗" + module + errCode + errMsg);
                if (null != mGuestLiveView) {
                    mGuestLiveView.enterRoomComplete(false);
                }
            }
        });
    }

3.加入房間成功后,在房間發(fā)送自定義消息,告訴房間內(nèi)有人加入,方便主播統(tǒng)計目前的房間人數(shù)。

 /**
     * 發(fā)送信令,點贊,進(jìn)入房間
     */

    public int sendGroupCmd(int cmd, String param) {
        ILVCustomCmd customCmd = new ILVCustomCmd();
        //1.傳cmd
        customCmd.setCmd(cmd);
        //2.創(chuàng)建自定義數(shù)據(jù)
        String json = sendCusJsn();
        //3.傳自定義數(shù)據(jù)
        customCmd.setParam(json);
        //4.設(shè)置自定義類型
        customCmd.setType(ILVText.ILVTextType.eGroupMsg);
        return sendCmd(customCmd);
    }

4.退出房間前:發(fā)送房間自定義消息,告訴主播觀眾退出,方便主播統(tǒng)計目前的在線人數(shù)。

5.退出直播間。

  public void startExitRoom() {
        if (ILiveSDK.getInstance() != null && ILiveSDK.getInstance().getAvVideoCtrl() != null) {
            ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
        }
        quitLiveRoom();
    }
 private void quitLiveRoom() {
        ILVLiveManager.getInstance().quitRoom(new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                //1.回調(diào)給頁面
                if (null != mGuestLiveView) {
                    LogUtils.d("suyan", "=========退出成功");
                    mGuestLiveView.quiteRoomComplete(true);
                }
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
                if (null != mGuestLiveView) {
                    mGuestLiveView.quiteRoomComplete(false);
                }
            }
        });
    }

自己封裝了一個觀眾端的使用類,也是根據(jù)互動直播demo改的

package com.jyjt.ydyl.txlive.presenters;

import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.jyjt.ydyl.Entity.LiveCusEntity;
import com.jyjt.ydyl.tools.ConfigUtils;
import com.jyjt.ydyl.tools.Constans;
import com.jyjt.ydyl.tools.LogUtils;
import com.jyjt.ydyl.tools.ToastUtil;
import com.jyjt.ydyl.txlive.LiveDetailActivity;
import com.jyjt.ydyl.txlive.event.LiveMessageEvent;
import com.jyjt.ydyl.txlive.views.GuestLiveView;
import com.tencent.TIMConversationType;
import com.tencent.TIMCustomElem;
import com.tencent.TIMElem;
import com.tencent.TIMElemType;
import com.tencent.TIMGroupSystemElem;
import com.tencent.TIMGroupSystemElemType;
import com.tencent.TIMMessage;
import com.tencent.av.sdk.AVRoomMulti;
import com.tencent.ilivesdk.ILiveCallBack;
import com.tencent.ilivesdk.ILiveConstants;
import com.tencent.ilivesdk.ILiveSDK;
import com.tencent.ilivesdk.core.ILiveLog;
import com.tencent.ilivesdk.core.ILiveRoomManager;
import com.tencent.ilivesdk.core.ILiveRoomOption;
import com.tencent.livesdk.ILVCustomCmd;
import com.tencent.livesdk.ILVLiveConstants;
import com.tencent.livesdk.ILVLiveManager;
import com.tencent.livesdk.ILVLiveRoomOption;
import com.tencent.livesdk.ILVText;
import com.tencent.openqq.protocol.imsdk.im_common;

import org.json.JSONObject;
import org.json.JSONTokener;

import java.util.Observable;
import java.util.Observer;

/**
 * 觀眾端,直播幫助類
 * <p>
 * 1.加入房間
 * 2.離開房間
 * 3.發(fā)送點贊,和加入房間指令
 * 4.解析傳過來的自定義消息(點贊,其他成員加入,用戶退出直播, 達(dá)到上限,主播回來了)
 * 5.房間鏈接斷開監(jiān)聽
 * <p>
 * 蘇艷
 */

public class GuestLiveHelper implements ILiveRoomOption.onRoomDisconnectListener, Observer {
    private final String TAG = "LiveHelper";
    private GuestLiveView mGuestLiveView;
    public Activity mContext;
    private String rommId;
    Gson gson;

    public GuestLiveHelper(Activity context, GuestLiveView mGuestLiveView) {
        mContext = context;
        this.mGuestLiveView = mGuestLiveView;
        LiveMessageEvent.getInstance().addObserver(this);
        GsonBuilder builder = new GsonBuilder();
        gson = builder.create();
    }

    //加入房間
    public void joinRoom(int roomId) {
        ILVLiveManager.getInstance().quitRoom(null);
        this.rommId = roomId + "";
        ILVLiveRoomOption memberOption = new ILVLiveRoomOption(ConfigUtils.getUid())
                .autoCamera(false)
                .roomDisconnectListener(this)
                .videoMode(ILiveConstants.VIDEOMODE_BSUPPORT)
                .controlRole("Guest")
                .authBits(AVRoomMulti.AUTH_BITS_JOIN_ROOM | AVRoomMulti.AUTH_BITS_RECV_AUDIO | AVRoomMulti.AUTH_BITS_RECV_CAMERA_VIDEO | AVRoomMulti.AUTH_BITS_RECV_SCREEN_VIDEO)
                .videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO)
                .autoMic(false);
        int ret = ILVLiveManager.getInstance().joinRoom(roomId, memberOption, new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                ILiveLog.d(TAG, "ILVB-Suixinbo|startEnterRoom->join room sucess");
                if (null != mGuestLiveView)

                    mGuestLiveView.enterRoomComplete(true);
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
                LogUtils.d("suyan", "===========加入房間失敗" + module + errCode + errMsg);
                if (null != mGuestLiveView) {
                    mGuestLiveView.enterRoomComplete(false);
                }
            }
        });
    }

    //退出房間
    public void startExitRoom() {
        if (ILiveSDK.getInstance() != null && ILiveSDK.getInstance().getAvVideoCtrl() != null) {
            ILiveSDK.getInstance().getAvVideoCtrl().setLocalVideoPreProcessCallback(null);
        }
        quitLiveRoom();
    }

    private void quitLiveRoom() {
        ILVLiveManager.getInstance().quitRoom(new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                //1.回調(diào)給頁面
                if (null != mGuestLiveView) {
                    LogUtils.d("suyan", "=========退出成功");
                    mGuestLiveView.quiteRoomComplete(true);
                }
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
                if (null != mGuestLiveView) {
                    mGuestLiveView.quiteRoomComplete(false);
                }
            }
        });
    }


    /**
     * 發(fā)送信令,點贊,進(jìn)入房間
     */

    public int sendGroupCmd(int cmd, String param) {
        ILVCustomCmd customCmd = new ILVCustomCmd();
        //1.傳cmd
        customCmd.setCmd(cmd);
        //2.創(chuàng)建自定義數(shù)據(jù)
        String json = sendCusJsn();
        //3.傳自定義數(shù)據(jù)
        customCmd.setParam(json);
        //4.設(shè)置自定義類型
        customCmd.setType(ILVText.ILVTextType.eGroupMsg);
        return sendCmd(customCmd);
    }

    //邀請上麥用的,暫時用不到
    public int sendC2CCmd(final int cmd, String param, String destId) {
        ILVCustomCmd customCmd = new ILVCustomCmd();
        customCmd.setDestId(destId);
        customCmd.setCmd(cmd);
        customCmd.setParam(param);
        customCmd.setType(ILVText.ILVTextType.eC2CMsg);
        return sendCmd(customCmd);
    }

    private int sendCmd(final ILVCustomCmd cmd) {
        return ILVLiveManager.getInstance().sendCustomCmd(cmd, new ILiveCallBack() {
            @Override
            public void onSuccess(Object data) {
                LogUtils.d(TAG, "suyan" + cmd.getCmd() + "|" + cmd.getParam() + "====發(fā)送成功");
            }

            @Override
            public void onError(String module, int errCode, String errMsg) {
//                Toast.makeText(mContext, "sendCmd->failed:" + module + "|" + errCode + "|" + errMsg, Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onRoomDisconnect(int errCode, String errMsg) {
        if (null != mGuestLiveView) {
            //房間異常退出,通常指斷網(wǎng),或者沒電
            mGuestLiveView.roomDisconnect(errCode, errMsg);
        }
    }

    // 解析文本消息
    private void processTextMsg(LiveMessageEvent.SxbMsgInfo info) {
        if (null == info.data || !(info.data instanceof ILVText)) {
            return;
        }
        ILVText text = (ILVText) info.data;
        if (text.getType() == ILVText.ILVTextType.eGroupMsg
                && !text.getDestId().equals(rommId)) {
            if (TextUtils.isEmpty(rommId)) {
                LogUtils.d("suyan","========當(dāng)前的roomid為空");
            }
            return;
        }

        String name = info.senderId;
        if (null != info.profile && !TextUtils.isEmpty(info.profile.getNickName())) {
            name = info.profile.getNickName();
        }

        if (null != mGuestLiveView)
            mGuestLiveView.refreshText(text.getText(), name);
    }

    // 解析自定義信令
    private void processCmdMsg(LiveMessageEvent.SxbMsgInfo info) {
        if (null == info.data || !(info.data instanceof ILVCustomCmd)) {
            return;
        }
        ILVCustomCmd cmd = (ILVCustomCmd) info.data;
        if (cmd.getType() == ILVText.ILVTextType.eGroupMsg
                && !cmd.getDestId().equals(rommId)) {
            if (TextUtils.isEmpty(rommId)) {
                LogUtils.d("suyan","========當(dāng)前的roomid為空");
            }
            return;
        }

        String name = info.senderId;
        if (null != info.profile && !TextUtils.isEmpty(info.profile.getNickName())) {
            name = info.profile.getNickName();
        }

        handleCustomMsg(cmd.getCmd(), cmd.getParam(), info.senderId, name);
    }

    private void processOtherMsg(LiveMessageEvent.SxbMsgInfo info) {
        if (null == info.data || !(info.data instanceof TIMMessage)) {
            return;
        }
        TIMMessage currMsg = (TIMMessage) info.data;

        // 過濾非當(dāng)前群組消息
        if (currMsg.getConversation() != null && currMsg.getConversation().getPeer() != null) {
            if (currMsg.getConversation().getType() == TIMConversationType.Group
                    && !rommId.equals(currMsg.getConversation().getPeer())) {
                if (TextUtils.isEmpty(rommId)) {
                    LogUtils.d("suyan","========當(dāng)前的roomid為空");
                }
                return;
            }
        }

        for (int j = 0; j < currMsg.getElementCount(); j++) {
            if (currMsg.getElement(j) == null)
                continue;
            TIMElem elem = currMsg.getElement(j);
            TIMElemType type = elem.getType();


            //系統(tǒng)消息
            if (type == TIMElemType.GroupSystem) {  // 群組解散消息
                if (TIMGroupSystemElemType.TIM_GROUP_SYSTEM_DELETE_GROUP_TYPE == ((TIMGroupSystemElem) elem).getSubtype()) {
                    if (null != mGuestLiveView)
                        mGuestLiveView.dismissGroup("host", null);
                }
            } else if (type == TIMElemType.Custom) {
                try {
                    final String strMagic = "__ACTION__";
                    String customText = new String(((TIMCustomElem) elem).getData(), "UTF-8");
                    if (!customText.startsWith(strMagic))   // 檢測前綴
                        continue;
                    JSONTokener jsonParser = new JSONTokener(customText.substring(strMagic.length() + 1));
                    JSONObject json = (JSONObject) jsonParser.nextValue();
                    String action = json.optString("action", "");
                    if (action.equals("force_exit_room") || action.equals("force_disband_room")) {
                        JSONObject objData = json.getJSONObject("data");
                        String strRoomNum = objData.optString("room_num", "");
                        if (strRoomNum.equals(String.valueOf(ILiveRoomManager.getInstance().getRoomId()))) {
                            if (null != mGuestLiveView) {
                                mGuestLiveView.forceQuitRoom("管理員已將房間解散或?qū)⒛叱龇块g!");
                            }
                        }
                    }
                } catch (Exception e) {
                }
            }
        }
    }

    @Override
    public void update(Observable observable, Object o) {

        LiveMessageEvent.SxbMsgInfo info = (LiveMessageEvent.SxbMsgInfo) o;
        LogUtils.d("suyan", "======收到消息" + info.msgType);
        switch (info.msgType) {
            case LiveMessageEvent.MSGTYPE_TEXT:
                processTextMsg(info);
                break;
            case LiveMessageEvent.MSGTYPE_CMD:
                processCmdMsg(info);
                break;
            case LiveMessageEvent.MSGTYPE_OTHER:
                LogUtils.d("suyan", "=======其他消息");
                processOtherMsg(info);
            case LiveMessageEvent.MSGTYPE_DDETAIL:
                LogUtils.d("suyan", "=======刷新房間數(shù)據(jù)");
                processDetailMsg(info);
                break;
        }
    }

    // 解析自定義信令
    private void processDetailMsg(LiveMessageEvent.SxbMsgInfo info) {
        if (null != info && !TextUtils.isEmpty(info.senderId)) {
            if (null != mGuestLiveView) {
                mGuestLiveView.refreshData(info.senderId);
            }
        }

    }

    private void handleCustomMsg(int action, String param, String identifier, String nickname) {
        LogUtils.d(TAG, "handleCustomMsg->action: " + action);
        if (null == mGuestLiveView) {
            return;
        }
        LiveCusEntity mliveCusEntity = updateGson(param);
        if (mliveCusEntity != null) {
            LogUtils.d("suyan", "===========解析后的數(shù)據(jù)" + mliveCusEntity.getContent().getLike_num() + "=" + mliveCusEntity.getContent().getOnline_num() + "=" + mliveCusEntity.getUser().getUid());
        }
        switch (action) {
            case Constans.AVIMCMD_PRAISE://點贊
                mGuestLiveView.refreshGoodLike();
                break;
            case Constans.AVIMCMD_ENTERLIVE://其他成員加入
                mGuestLiveView.memberJoin(identifier, nickname);
                break;
            case Constans.AVIMCMD_EXITLIVE: //用戶退出直播
                mGuestLiveView.hostOffline(identifier, nickname);
                break;
            case ILVLiveConstants.ILVLIVE_CMD_LINKROOM_LIMIT:   // 達(dá)到上限
                LogUtils.d("suyan","========到達(dá)上限");
                break;
            case Constans.AVIMCMD_HOST_BACK://主播回來了
                LogUtils.d("suyan","========主播回來了");
                //昵稱:nickname  id:identifier
            case Constans.AVIMCMD_PRAISE_TOTAL://觀眾進(jìn)入房間,主播發(fā)送現(xiàn)在的總點贊數(shù)
                try {
                    if (mliveCusEntity != null && !TextUtils.isEmpty(mliveCusEntity.getContent().getLike_num())) {
                        mGuestLiveView.refreshTotalGoodLike(Long.parseLong(mliveCusEntity.getContent().getLike_num()));
                    }
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            default:
                break;
        }
    }

    //解析jsong
    public LiveCusEntity updateGson(String jsonTest) {
        if (!TextUtils.isEmpty(jsonTest)) {
            LiveCusEntity mLiveCusEntity;
            try {
                mLiveCusEntity = gson.fromJson(jsonTest, LiveCusEntity.class);
                return mLiveCusEntity;
            } catch (JsonSyntaxException e) {
                e.printStackTrace();
                return null;
            }
        } else {
            return null;
        }
    }

    //發(fā)送json
    public String sendCusJsn() {
        LiveCusEntity liveCusEntity = new LiveCusEntity();
        LiveCusEntity.LiveContent mLiveContent = new LiveCusEntity.LiveContent();
        liveCusEntity.setContent(mLiveContent);
        LiveCusEntity.User mUser = new LiveCusEntity.User();
        mUser.setUid(ConfigUtils.getUid());
        liveCusEntity.setUser(mUser);
        String json = gson.toJson(liveCusEntity, LiveCusEntity.class);
        LogUtils.d("suyan", "==========自定義數(shù)據(jù)" + json);
        return json;
    }

    //銷毀
    public void onDestory() {
        mGuestLiveView = null;
        mContext = null;
        LiveMessageEvent.getInstance().deleteObserver(this);
        ILVLiveManager.getInstance().quitRoom(null);
        ILiveRoomManager.getInstance().onDestory();
        ILVLiveManager.getInstance().onDestory();
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 主要功能 互動直播定義 互動直播(Interactive Live Video Broadcasting),顧名思...
    0蛐蛐0閱讀 6,313評論 1 8
  • 全局創(chuàng)建context? 創(chuàng)建一個全局的context,然后退出SDK層房間時不銷毀只是停止context。 SD...
    Carden閱讀 1,860評論 0 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,039評論 25 709
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,992評論 2 59
  • 啟動電腦發(fā)現(xiàn),搜狗拼音出問題了。圖標(biāo)變大,輸入沒有選項。經(jīng)查找,如下步驟解決問題 1,刪除~/.config目錄下...
    南王農(nóng)夫閱讀 1,006評論 0 0

友情鏈接更多精彩內(nèi)容