最佳实践-p2p android聊天app源码

     github源码下载地址  点击下载  

     

     本例子用于举例说明,利用kkp2p sdk来开发一个聊天app,该聊天app支持文字聊天以及传输图片文件。需要注意的是,本例子仅仅是一个demo,主要是说明kkp2p sdk在app中的基本用法,有些细节考虑不完善,请大家自行修改。该例子有少量代码拷贝于网上,感谢这些乐于分享的朋友。

     使用局域网搜索模式无须部署云端服务。


一、app界面说明

主界面图片如下所示:

  


如上所示,界面上有云端的登录域名和端口号默认值,局域网搜索端口默认值

connect mode表示建连模式,缺省值是p2p模式,点击button有其他一些模式选择

account|sercret是登录账号和密码默认值。

如果使用局域网搜索模式,则无须部署云端服务,云端的登录域名和端口信息会被忽略掉。


点击login后就进入聊天界面,具体如下所示:



如上图所示,首先需要点击connect和目标端建连,建连成功后界面会弹出提示

点击最下面的file,可以从相册选择图片发送到目标端。

可以从输入框输入文件,然后点击send将文字发送到目标端。

二、代码简要说明

首先看MainActivity.java源码



package com.example.kkp2pjni;

import com.google.gson.Gson;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

import android.view.Gravity;

import android.view.View;

import android.widget.ArrayAdapter;

import android.widget.Button;

import android.widget.EditText;

import android.widget.Spinner;

import android.widget.TextView;

import android.widget.Toast;


import com.example.kkp2pjni.databinding.ActivityMainBinding;


public class MainActivity extends AppCompatActivity {


    // Used to load the 'kkp2pjni' library on application startup.

    static {

        // 需要用jni将libkkp2p.so中接口在封装一层给java调用,可以参考工程文件中的例子

        System.loadLibrary("kkp2pjni");

    }


    private ActivityMainBinding binding;


    private KKP2PEngine p2pEngine;

    private long p2pHandle;

    KKP2PConnectCtx connect_ctx;

    KKP2PChannel channel;


    private String src_peer_id;

    private String src_peer_key;

    private EditText login_domain;

    private EditText login_port;

    private EditText lan_port;

    private Spinner connect_mode;

    private Spinner account_info;

    Intent intent;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(binding.getRoot());


        intent = null;


        // assign widget

        login_domain =  (EditText)findViewById(R.id.editDomain);

        login_port = (EditText)findViewById(R.id.editPort);

        lan_port =  (EditText)findViewById(R.id.editLanPort);


        // set select connect_mode

        connect_mode = (Spinner) findViewById(R.id.spinnerMode);

        String strConnMode="auto|0,p2p|1,relay|2,lanSearch";

        String[] arrConnMode = strConnMode.split(",");


        ArrayAdapter<String> adapterMode;

        adapterMode = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, arrConnMode);

        adapterMode.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        connect_mode.setAdapter(adapterMode);

        connect_mode.setSelection(1);

        connect_mode.setVisibility(View.VISIBLE);


        // set select account_info

        account_info = (Spinner) findViewById(R.id.spinnerAccount);

        String strAccount="kkuai-ipc-00001|WtXmjG,kkuai-ipc-00002|OBq26M";

        String[] arrAccount = strAccount.split(",");


        ArrayAdapter<String> adapterAccount;

        adapterAccount = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, arrAccount);

        adapterAccount.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        account_info.setAdapter(adapterAccount);

        account_info.setVisibility(View.VISIBLE);

        p2pEngine = new KKP2PEngine();

        p2pHandle = 0 ;

        Button bt = findViewById(R.id.button);

        bt.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {

                // 初始化kkp2p sdk

                KKP2PConfig config = new KKP2PConfig();

                config.login_domain = login_domain.getText().toString();

                config.login_port =  Integer.parseInt(login_port.getText().toString());

                config.lan_search_port = Integer.parseInt(lan_port.getText().toString());

                config.log_path = null;

                config.max_log_size = 1024*1024;

                if (p2pHandle != 0) {

                    p2pEngine.nv_kkp2p_engine_destroy(p2pHandle);

                }

                p2pHandle = p2pEngine.nv_kkp2p_engine_init(config, 5000);

                if (p2pHandle == 0) {

                    Toast toast = Toast.makeText(MainActivity.this, "p2p engine init error",

                            Toast.LENGTH_LONG);

                    toast.setGravity(Gravity.CENTER,0,0);

                    toast.show();

                    return;

                }


                // get connect param ctx

                Boolean lanSearch = new Boolean("false");

                connect_ctx = new KKP2PConnectCtx();

                connect_ctx.connect_mode = 0;


                // get connect mode

                String strConnect = connect_mode.getSelectedItem().toString();

                String[] strConnArray = strConnect.split("\\|");

                if (strConnArray.length == 2) {

                    connect_ctx.connect_mode = Integer.parseInt(strConnArray[1]);

                } else if (strConnArray[0].equals("lanSearch")) {

                    connect_ctx.connect_mode = 1;

                    lanSearch = true;

                }


                // get src peer id and dest peerId

                String strAccount = account_info.getSelectedItem().toString();

                String[] strAccountArray = strAccount.split("\\|");

                src_peer_id =  strAccountArray[0];

                src_peer_key = strAccountArray[1];


                // 缺省的登录账号

                if (src_peer_id.equals("kkuai-ipc-00001")) {

                    connect_ctx.peer_id = "kkuai-ipc-00002";

                } else {

                    connect_ctx.peer_id = "kkuai-ipc-00001";

                }

                connect_ctx.encrypt_data = 0;

                connect_ctx.time_out = 5000;

                connect_ctx.func = null;

                connect_ctx.func_param = null;


                // 加入到云端网络

                int result = p2pEngine.nv_kkp2p_join_net(p2pHandle, src_peer_id, src_peer_key);

                if (result < 0) {

                    Toast.makeText(MainActivity.this, "kkp2p join net error",

                            Toast.LENGTH_SHORT).show();

                    return;

                }


                // 加入到局域网络

                result =  p2pEngine.nv_kkp2p_join_lan(p2pHandle, src_peer_id);

                if (result < 0) {

                    Toast.makeText(MainActivity.this, "kkp2p join lan error",

                            Toast.LENGTH_SHORT).show();

                    return;

                }


                // start the chat activity

                if (intent == null) {

                    intent = new Intent(MainActivity.this, ChatActivity.class);

                }


                // put data to chat activity

                Gson gs = new Gson();

                String targetCtx = gs.toJson(connect_ctx);

                intent.putExtra("extraConnectCtx",  targetCtx);

                intent.putExtra("extraP2PHandle", p2pHandle);

                intent.putExtra("lanSearch", lanSearch);


                startActivity(intent);

            }

        });

    }

}


再看ChatActivity.java源码

package com.example.kkp2pjni;


import androidx.activity.result.ActivityResultLauncher;

import androidx.activity.result.contract.ActivityResultContract;

import androidx.activity.result.contract.ActivityResultContracts;

import androidx.annotation.RequiresApi;

import androidx.appcompat.app.AppCompatActivity;

import androidx.recyclerview.widget.LinearLayoutManager;

import androidx.recyclerview.widget.RecyclerView;


import android.Manifest;

import android.annotation.SuppressLint;

import android.content.ContentUris;

import android.content.Context;

import android.content.Intent;

import android.content.pm.PackageManager;

import android.database.Cursor;

import android.net.Uri;

import android.os.AsyncTask;

import android.os.Build;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.provider.DocumentsContract;

import android.provider.MediaStore;

import android.util.Log;

import android.view.Gravity;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.ProgressBar;

import android.widget.TextView;

import android.widget.Toast;


import com.google.gson.Gson;


import java.io.File;

import java.io.FileInputStream;

import java.util.ArrayList;


public class ChatActivity<Unit> extends AppCompatActivity {

    private KKP2PEngine p2pEngine;


    // clientChannel can read and wirte data

    private KKP2PChannel clientChannel;


    // acceptChannel can read and write data too

    private ArrayList<KKP2PChannel> acceptChannelArray;

    private long p2pHandle;

    private int listenFd;

    private Button picButton;

    private Button sendButton;

    private TextView msgText;

    private RecyclerView recyclerView;

    private ArrayList<Msg> msgList;

    private MsgAdapter msgAdapter;

    private ProgressBar progressBar;


    private KKP2PConnectCtx connect_ctx;

    private boolean lanSearch;

    private Thread accept_thread;

    private boolean accept_exit;

    private Thread recv_thread;

    private SocketHandler mHandler;


    @RequiresApi(api = Build.VERSION_CODES.M)

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_chat);

        // init view

        msgText = (EditText)findViewById(R.id.input_text);

        sendButton = (Button)findViewById(R.id.send);

        picButton = (Button)findViewById(R.id.pic);


        // init recycler

        recyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);

        msgList = new ArrayList<Msg>();

        msgAdapter = new MsgAdapter(msgList);

        recyclerView.setAdapter(msgAdapter);

        LinearLayoutManager manager = new LinearLayoutManager(this);

        manager.setOrientation(LinearLayoutManager.VERTICAL);

        recyclerView.setLayoutManager(manager);


        // init progressBar

        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        progressBar.setVisibility(View.GONE);


        mHandler = new SocketHandler();


        // init p2p engine

        p2pEngine = new KKP2PEngine();

        connect_ctx = new KKP2PConnectCtx();

        clientChannel = new KKP2PChannel();

        acceptChannelArray = new ArrayList<KKP2PChannel>();


        Intent intent = getIntent();

        String strCtx = intent.getStringExtra("extraConnectCtx");

        Gson gs = new Gson();

        connect_ctx = gs.fromJson(strCtx, KKP2PConnectCtx.class);

        p2pHandle = intent.getLongExtra("extraP2PHandle",0);

        listenFd = p2pEngine.nv_kkp2p_listen_fd(p2pHandle);

        lanSearch = intent.getBooleanExtra("lanSearch",false);

        Log.d("KKP2P","lanSearch:" + lanSearch);


        // set friend account

        Button firendBT = (Button) findViewById(R.id.friend);

        firendBT.setText(connect_ctx.peer_id);


        // set connect listen

        Button btConnect = findViewById(R.id.connect);

        btConnect.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {

                // 主动创建连接

                int result = -1;

                if (lanSearch) {

                    result = p2pEngine.nv_kkp2p_lan_search(p2pHandle, connect_ctx, clientChannel);

                } else {

                    result = p2pEngine.nv_kkp2p_connect(p2pHandle, connect_ctx, clientChannel);

                }

                if (result < 0) {

                    Toast toast = Toast.makeText(ChatActivity.this, "kkp2p connect error",

                            Toast.LENGTH_LONG);

                    toast.setGravity(Gravity.CENTER,0,0);

                    toast.show();

                    return;

                } else {

                    Toast toast = Toast.makeText(ChatActivity.this, "kkp2p connect success",

                            Toast.LENGTH_LONG);

                    toast.setGravity(Gravity.CENTER,0,0);

                    toast.show();

                }

                Button connetBT = (Button) findViewById(R.id.connect);

                if (clientChannel.transmit_mode == 1) {

                    connetBT.setText("p2p");

                } else {

                    connetBT.setText("relay");

                }

                connetBT.setEnabled(false);

                // begin recv data from channel

                StartRecvThread();

            }

        });



        sendButton.setOnClickListener(new View.OnClickListener() {

            @SuppressLint("StaticFieldLeak")

            public void onClick(View v) {

                new AsyncTask<String, Integer, String>() {

                    protected String doInBackground(String... params) {

                        int result = 0;

                        if (getAcceptChannel() != null && getAcceptChannel().fd > 0) {

                            result = sendMsg(getAcceptChannel());

                            if (result <0) {

                                KKP2PEngine.nv_kkp2p_close_fd(getAcceptChannel().fd);

                                KKP2PEngine.nv_kkp2p_close_channel(p2pHandle, getAcceptChannel().channel_id);

                                getAcceptChannel().fd = -1;

                                getAcceptChannel().channel_id = 0;

                            }

                        } else if (clientChannel.fd > 0) {

                            result = sendMsg(clientChannel);

                            if (result < 0 ) {

                                KKP2PEngine.nv_kkp2p_close_fd(clientChannel.fd);

                                KKP2PEngine.nv_kkp2p_close_channel(p2pHandle,clientChannel.channel_id);

                                clientChannel.fd = -1;

                                clientChannel.channel_id = 0;

                            }

                        } else {

                            return null;

                        }


                        if (result < 0 ) {

                            Message msg = mHandler.obtainMessage();

                            String strDesc = "kkp2p_write error, close fd and channel";

                            msg.what = 4;

                            msg.obj = strDesc;

                            msg.sendToTarget();

                        }

                        return null;

                    }

                }.execute();

            }

        });


        // on pic button

        int REQUEST_CODE_CONTACT = 101;

        String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE};

        for (String str : permissions) {

            if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {

                this.requestPermissions(permissions, REQUEST_CODE_CONTACT);

            }

        }

        ActivityResultLauncher activityResultLauncher = registerForActivityResult(

                new ResultContract(),

                result -> {

                    Toast toast = Toast.makeText(ChatActivity.this, result,

                            Toast.LENGTH_LONG);

                    toast.setGravity(Gravity.CENTER,0,0);

                    toast.show();

                    String imagePath = result;

                    new AsyncTask<String, Integer, String>() {

                        protected String doInBackground(String... params) {

                            sendMediaFile(params[0]);

                            return null;

                        }

                    }.execute(imagePath);

                });


        picButton.setOnClickListener(new View.OnClickListener() {

                @Override

                public void onClick(View v) {

                    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

                    intent.setType("video/*;image/*");

                    activityResultLauncher.launch(intent);

                }

            });


        // 启动单独线程接收远程连接

        accept_exit = false;

        StartAcceptThread();

    }


    // 如果有多个连接,则获取一个最新的连接

    private KKP2PChannel getAcceptChannel() {

        int count = acceptChannelArray.size();

        if (count > 0) {

            return acceptChannelArray.get(count - 1);

        }

        return null;

    }


    protected int sendSocketMsg(KKP2PChannel channel, byte[] byteArray, int expectLen) {

        int snd = 0 ;

        int sended = 0;


        snd = p2pEngine.nv_kkp2p_write(channel.fd, byteArray, expectLen,5000);

        if (snd < 0 ) {

            return -1;

        }

        sended += snd;

        while ( sended < expectLen) {

            byte[] tmpArray = new byte[expectLen - sended];

            System.arraycopy(byteArray,sended,tmpArray,0,expectLen - sended);

            snd = p2pEngine.nv_kkp2p_write(channel.fd, tmpArray, tmpArray.length,5000);

            if (snd < 0 ) {

                return -1;

            }

            sended += snd;

        }

        return sended;

    }


    protected int recvSocketMsg(KKP2PChannel channel,byte[] byteArray, int expectLen) {

        int recv = 0 ;

        int recved = 0;


        recv = p2pEngine.nv_kkp2p_read(channel.fd, byteArray, expectLen,5000);

        if (recv < 0 ) {

            Log.e("KKP2P","nv_kkp2p_read error,error code:" + recv);

            return -1;

        }

        recved += recv;

        while (recved < expectLen) {

            byte[] tmpArray = new byte[expectLen - recved];

            recv = p2pEngine.nv_kkp2p_read(channel.fd, tmpArray, tmpArray.length,5000);

            if (recv < 0 ) {

                Log.e("KKP2P","nv_kkp2p_read error,error code:" + recv);

                return -1;

            }

            System.arraycopy(tmpArray,0,byteArray,recved,recv);

            recved += recv;

        }

        return recved;

    }


    // 发送文本消息

    protected int sendMsg(KKP2PChannel channel) {

        int result = 0;

        try {

            String text = msgText.getText().toString().trim();

           // 协议格式为TLV

         // T表示tag,1是文本消息,2是文件消息

         // L表示长度

         // V表示内容

            byte[] tagArray = ByteConvert.uintToBytes(1);

            result = sendSocketMsg(channel,tagArray,tagArray.length);

            if (result < 0) {

                return result;

            }

            // send length

            byte[] byteArray = text.getBytes();

            byte[] lengthArray = ByteConvert.uintToBytes(byteArray.length);

            result = sendSocketMsg(channel,lengthArray,lengthArray.length);

            if (result < 0) {

                return result;

            }


            // send value

            result = sendSocketMsg(channel,byteArray,byteArray.length);


            if (result < 0) {

                return result;

            }


            Message message = new Message();

            message.what = Msg.TYPE_SENT;

            message.obj = text;

            mHandler.sendMessage(message);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }


    protected int sendMediaFile(String imagePath) {

        int result = 0;

        if (getAcceptChannel() != null && getAcceptChannel().fd > 0) {

            result = sendFile(getAcceptChannel(),imagePath);

            if (result < 0) {

                KKP2PEngine.nv_kkp2p_close_fd(getAcceptChannel().fd);

                KKP2PEngine.nv_kkp2p_close_channel(p2pHandle, getAcceptChannel().channel_id);

                getAcceptChannel().fd = -1;

                getAcceptChannel().channel_id = 0;

            }

        } else if (clientChannel.fd > 0) {

            result = sendFile(clientChannel,imagePath);

            if (result < 0 ) {

                KKP2PEngine.nv_kkp2p_close_fd(clientChannel.fd);

                KKP2PEngine.nv_kkp2p_close_channel(p2pHandle,clientChannel.channel_id);

                clientChannel.fd = -1;

                clientChannel.channel_id = 0;

            }

        }


        if (result < 0 ) {

            Message msg = mHandler.obtainMessage();

            String strDesc = "kkp2p_write error, close fd and channel";

            msg.what = 4;

            msg.obj = strDesc;

            msg.sendToTarget();

        }

        return result;

    }


    // 发送文件内容

    protected int sendFile(KKP2PChannel channel,String imagePath) {

        Log.d("KKP2P","begin send file:" + imagePath);

        int result = 0;

        try {

            File file = new File(imagePath);

            long fileLen = file.length();


            // the protocol format is TLV:tag,lenth,value

            // send file tag is 2

            byte[] tagArray = ByteConvert.uintToBytes(2);

            result = sendSocketMsg(channel,tagArray,tagArray.length);

            if (result < 0) {

                return result;

            }

            // send length

            byte[] lengthArray = ByteConvert.uintToBytes(fileLen);

            result = sendSocketMsg(channel,lengthArray,lengthArray.length);

            if (result < 0) {

                return result;

            }


            long startMs = System.currentTimeMillis();

            byte[] buffer = new byte[1024];

            int len = 0;

            FileInputStream inStream = new FileInputStream(file);

            int totalSended = 0 ;

            while( (len = inStream.read(buffer))!= -1) {

                result = sendSocketMsg(channel,buffer,len);

                if (result < 0) {

                    return result;

                }

                totalSended += len;


                // 更新发送文件的进度条

                Message message = Message.obtain();

                message.obj = (int)((long)totalSended * 100 /fileLen);

                message.what = 5;

                mHandler.sendMessage(message);

            }


            // send success

            long endMs = System.currentTimeMillis();

            Message message = Message.obtain();


            String strDesc = "send file len:"+ fileLen

                    + ",speed:" + fileLen*1000/(endMs-startMs) + " Byte/s";


            message.obj = strDesc;

            message.what = 6;

            mHandler.sendMessage(message);

            inStream.close();

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }


    // 接收远端的数据,文本或者文件

    private void LoopReadChannel(KKP2PChannel channel) {

        // loop recv msg

        int recvLen = 0;

        while (true) {

            recvLen = recvMsgAndFile(channel);

            if (recvLen < 0) {

                String strDesc = "kkp2p_read error, close fd and channel,fd:"

                        + channel.fd + ",result:" + recvLen + ",channel hash code:"

                        + channel.hashCode();

                Log.e("KKP2P","LoopReadChannel:" + strDesc);


                // socket error

                p2pEngine.nv_kkp2p_close_channel(p2pHandle,channel.channel_id);

                p2pEngine.nv_kkp2p_close_fd(channel.fd);

                channel.fd = -1;

                channel.channel_id = 0;

                Message msg = mHandler.obtainMessage();

                msg.what = 4;

                msg.obj = strDesc;

                msg.sendToTarget();

                return;

            }

        }

    }


    protected int recvMsgAndFile(KKP2PChannel channel) {

        // recv tag

        byte[] tagArray = new byte[4];

        int result = recvSocketMsg(channel,tagArray,tagArray.length);

        if (result < 0 ) {

            return result;

        }

        long  tag = ByteConvert.bytesToUint(tagArray);


        // recv length

        byte []lenArray = new byte[4];

        result = recvSocketMsg(channel,lenArray,lenArray.length);

        if (result < 0 ) {

            return result;

        }

        long  length = ByteConvert.bytesToUint(lenArray);

        if (tag == 1) {

            // tag为1表示文本消息

            byte[] textArray = new byte[(int)length];

            result = recvSocketMsg(channel,textArray,textArray.length);

            if (result < 0 ) {

                return result;

            }

            Message msg = mHandler.obtainMessage();

            String text = new String(textArray,0,result);

            msg.what = Msg.TYPE_RECEIVED;

            msg.obj = text;

            msg.sendToTarget();

        } else if (tag == 2) {

           // tag为2表示文件

         // 例子将接收到的文件丢弃处理,如有必要大家可以完善逻辑将文件保存起来

            long startMs = System.currentTimeMillis();

            int  totalReceived = 0;

            byte[] byteArray = new byte[1024];

            while(totalReceived < length) {

                int expectLen= Math.min(1024, (int)length-totalReceived);

                result = recvSocketMsg(channel,byteArray, expectLen);

                if (result < 0 ) {

                    return result;

                }

                totalReceived += result;

            }

            long endMs = System.currentTimeMillis();

            Message msg = mHandler.obtainMessage();

            String text = "recv file,len:" + totalReceived + ",speed:" +

                    (totalReceived/(endMs-startMs))*1000 + " Byte/s,discard it";

            msg.what = Msg.TYPE_RECEIVED;

            msg.obj = text;

            msg.sendToTarget();

        }

        return (int)length;

    }


    private void StartRecvThread() {

        // create accept thread to accept connect

        recv_thread = new Thread(new Runnable() {

            public void run() {

                LoopReadChannel(clientChannel);

            }

        });

        recv_thread.start();

    }


    // 接收远端的连接

    private void StartAcceptThread() {

        accept_thread = new Thread(new Runnable() {

            public void run() {

                while (true) {

                    int result = 0;

                    KKP2PChannel acceptChannel = new KKP2PChannel();

                    while (result == 0) {

                        // result为0表示超时

                        result = p2pEngine.nv_kkp2p_accept(p2pHandle,1000, acceptChannel);

                        if (result <0 || accept_exit) {

                            // error

                            Log.d("KKP2P","accept thread exit");

                            return ;

                        }

                    }


                    String strLog = "nv_kkp2p_accept a new channel success,"

                            + "fd:" + acceptChannel.fd + ",transmit_mode:"

                            + acceptChannel.transmit_mode + "," + ",p2pHandle"

                            + p2pHandle;


                    Log.d("KKP2P", strLog);

                    String strDesc = new String("");

                    if (acceptChannel.transmit_mode == 1) {

                        strDesc = "p2p(accepted)";

                    } else if (acceptChannel.transmit_mode == 2) {

                        strDesc = "relay(accepted)";

                    }

                    Message msg = mHandler.obtainMessage();

                    msg.what = 3;

                    msg.obj = strDesc;

                    msg.sendToTarget();

                    Thread recvThread = new Thread(new Runnable() {

                        public void run() {

                            // second, loop read from this channel

                            LoopReadChannel(acceptChannel);

                        }

                    });

                    recvThread.start();

                    // add to list

                    acceptChannelArray.add(acceptChannel);

                }

            }

        });

        accept_thread.start();

    }



    // 集中处理内部的消息

    class SocketHandler extends Handler {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            LinearLayoutManager manager;

            switch (msg.what) {

                case 0:

                    // recv msg

                    try {

                        String text = new String((String)msg.obj);

                        Msg chatMsg = new Msg(text, 0);

                        msgList.add(chatMsg);

                        Log.d("KKP2P","recv msg:"+text +",p2pHandle:"+ p2pHandle);

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    msgAdapter.notifyItemInserted(msgList.size()-1);

                    recyclerView.scrollToPosition(msgAdapter.getItemCount()-1);

                    break;

                case 1:

                    // send msg

                    try {

                        String text = new String((String)msg.obj);

                        Msg chatMsg = new Msg(text, 1);

                        msgList.add(chatMsg);

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    msgAdapter.notifyItemInserted(msgList.size()-1);

                    recyclerView.scrollToPosition(msgAdapter.getItemCount()-1);

                    msgText.setText("");

                    break;

                case 3:

                    // update button

                    try {

                        String text = new String((String)msg.obj);

                        String strLog = "accept channel type desc:" + text;

                        Button connetBT = (Button) findViewById(R.id.connect);

                        connetBT.setText(text);

                        connetBT.setEnabled(false);

                        String strToast = "accepted a new connection:" + text;

                        Toast toast = Toast.makeText(ChatActivity.this,

                                strToast, Toast.LENGTH_LONG);

                        toast.setGravity(Gravity.CENTER,0,0);

                        toast.show();


                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    break;

                case 4:

                    // notify close channel

                    try {

                        String text = new String((String)msg.obj);

                        Toast toast = Toast.makeText(ChatActivity.this,

                                text, Toast.LENGTH_LONG);

                        toast.setGravity(Gravity.CENTER,0,0);

                        toast.show();


                        Button connetBT = (Button) findViewById(R.id.connect);

                        connetBT.setText("disconnect");

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    break;

                case 5:

                    // update progress

                    try {

                        int percent = (int)msg.obj;

                        if (percent >0 && percent < 100) {

                            progressBar.setVisibility(View.VISIBLE);

                            progressBar.setProgress(percent);

                        } else {

                            progressBar.setVisibility(View.GONE);

                        }

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    break;

                case 6:

                    // update progress

                    try {

                        String text = new String((String)msg.obj);

                        Msg chatMsg = new Msg(text, 1);

                        msgList.add(chatMsg);

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                    msgAdapter.notifyItemInserted(msgList.size()-1);

                    recyclerView.scrollToPosition(msgAdapter.getItemCount()-1);

                    break;

                default:

                    break;

            }

        }

    }


    public class ResultContract extends ActivityResultContract<Intent, String> {

        Context myContext;


        public Intent createIntent(Context context, Intent input) {

            myContext = context;

            return new Intent(Intent.ACTION_GET_CONTENT).setType("video/*;image/*");

        }


        public String parseResult(int resultCode, Intent data) {

            Uri uri = data.getData();

            return PickUtils.getPath(myContext, uri);

        }

    }


    @Override

    protected void onDestroy() {

        super.onDestroy();

        accept_exit = true;

        Log.d("KKP2P","activity onDestroy");

    }


    public static String bytes2hex(byte[] bytes) {

        StringBuilder sb = new StringBuilder();

        String tmp;

        sb.append("[");

        int i = 0;

        for (byte b : bytes) {

            tmp = Integer.toHexString(0xFF & b);

            if (tmp.length() == 1) {

                tmp = "0" + tmp;

            }

            sb.append(tmp).append(" ");

        }

        sb.delete(sb.length() - 1, sb.length());

        sb.append("]");

        return sb.toString();

    }

}