android加密的即时通信软件 -客户端


在上学期,自己写了一个丑丑的加密即时通信软件作为网络安全课设。
能力有限,这个即时通信只限于局域网。
知识内容自行百度。
在这里插入图片描述

客户端

Activity

BaseEventActivity

package com.example.socketclient.Activity;


import android.os.Bundle;
import android.view.Window;

import androidx.appcompat.app.AppCompatActivity;

import org.greenrobot.eventbus.EventBus;

import butterknife.ButterKnife;


public abstract class BaseEventActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        getIntentData();
        setContentView(getLayoutResId());
        ButterKnife.bind(this);// 相当于 activity.subtitle = (TextView) activity.findViewById(R.id.subtitle);
        EventBus.getDefault().register(this);//注册订阅者
        init();
    }

    protected void getIntentData() {
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

    protected abstract void init();

    protected abstract int getLayoutResId();
}

FriendActivity

package com.example.socketclient.Activity;

import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import com.example.socketclient.R;
import com.example.socketclient.SQL.SQLiteHelper;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.ListItemAdapterUtil;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.SRConstantsUtil;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;


import javax.security.auth.Destroyable;

import butterknife.BindView;
import butterknife.OnClick;

public class FriendListActivity extends BaseEventActivity {
    @BindView(R.id.listview)
    ListView listView;
    @BindView(R.id.edt_friendAccount)
    EditText edt_friendAccount;

    private ProgressDialog mProgressDialog;//加载的小菊花
    //定义listView
    private ListItemAdapterUtil listItemAdapterUtil ;
    private String serverIp=null;
    private String username = null;
    private Socket socket = null;


    //定义线程
    sendConnectPacketThread sendcthread = new sendConnectPacketThread();
    //定义线程
    recPacketThread recpthread = new recPacketThread();

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_friend_list;
    }

    @Override
    protected void init() {
        Bundle bundle = this.getIntent().getExtras();
        serverIp = bundle.getString(ConstantUtil.SERVERIP);
        username = bundle.getString(ConstantUtil.USERNAME);
        MySocketApplication app = (MySocketApplication) getApplicationContext();//获取app
        socket = app.getSocket();//获取socket

        //初始化列表
        listItemAdapterUtil = new ListItemAdapterUtil(FriendListActivity.this,new String[]{"account"},
                R.layout.friend_item,new int[]{R.id.friend_item_title});
        getPrivateKey();
        showFriendList();//好友列表

        //启动 接收线程
        recpthread.start();

        //启动 发送连接包 线程
        sendcthread = new sendConnectPacketThread();
        String data = "{cmd:connect}{username:"+username+"}";
        sendcthread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
        //sendcthread.start();


        //表项监听事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Map<String,Object> map =  (Map <String,Object>)parent.getItemAtPosition(position);
                Toast.makeText(FriendListActivity.this, "好友 : "+map.get("account").toString(), Toast.LENGTH_SHORT).show();
                Bundle bundle = new Bundle();
                bundle.putString(ConstantUtil.FRIENDACCOUNT,map.get("account").toString());
                bundle.putString(ConstantUtil.SERVERIP,serverIp);
                bundle.putString(ConstantUtil.USERNAME,username);
                Intent intent = new Intent(FriendListActivity.this,Function_Socket_Client.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });


    }

    private void getPrivateKey() {

        //查询数据库中有无自己的 信息,没有添加信息,获取私钥,显示列表
        SQLiteDatabase db;
        ContentValues values;
        SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
        db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from keyInformation where USERNAME=? ",
                new String[]{username});
        if (cursor.getCount() == 0) {
            //无私钥就获取私钥
            String data = "{cmd:getPrivateKey}"+"{username:"+username+"}";
            sendDataPacketThread dataPacketThread = new sendDataPacketThread();
            dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
            dataPacketThread.start();
            //记得处理返回的信息,273行以后
        }
        cursor.close();
        db.close();
    }

    private void showFriendList() {
        //先移除所有好友项
        listItemAdapterUtil.deleteItem();
        //根据数据库添加好友项
        SQLiteDatabase db;
        ContentValues values;
        SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
        db = helper.getReadableDatabase();
        //Cursor cursor = db.rawQuery("select * from keyInformation ",null);
        Cursor cursor = db.query("keyInformation",
                null,null,null,null,null,null);
        if (cursor.getCount() != 0 ) {
            cursor.moveToFirst();
            //把第一个数据加上,即好友列表中有自己,自己可以和自己通讯
            //去掉下面这句话就可以不加上自己
            //listItemAdapterUtil.addItem(cursor.getString(1));
            while (cursor.moveToNext())
                listItemAdapterUtil.addItem(cursor.getString(1));
            listView.setAdapter(listItemAdapterUtil.getAdapter());
        }
    }

    private class recPacketThread extends Thread {
        public void run(){
            if (socket != null && socket.isConnected()) {
                Log.d("Function_Socket_Client", "开启接收线程");
                try {
                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String msg = "";
                    while (true) {
                        try {
                            if ((msg = br.readLine()).equals("")) ;
                            else {
                                Log.d("Function_Socket_Client", "接收到服务器数据(未解密):" + msg);
                                msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                                Log.d("Function_Socket_Client", "接收到服务器数据(已解密):" + msg);
                                EventBus.getDefault().post(msg);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if(socket.isClosed())break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    private class sendConnectPacketThread extends Thread {
        private String data;

        public void setData(String data) {
            this.data = data;
        }

        public void run(){
            if (socket != null && socket.isConnected()) {
                Log.d("Function_Socket_Client", "线程在线");
                //一开始延时5S执行
                try {
                    sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                while (true) {
                    //连接失败,退出线程
                    if(socket.isClosed()){
                        Log.d("Function_Socket_Client","该连接已终止!!!!!");
                        break;
                    }
                    //发送数据
                    try {
                        OutputStream ops = socket.getOutputStream();//得到socket输入流
                        OutputStreamWriter opsw = new OutputStreamWriter(ops);//
                        BufferedWriter writer = new BufferedWriter(opsw);
                        if (this.data != null){
                            writer.write(this.data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
                            writer.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //延时5S
                    try {
                        sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    private class sendDataPacketThread extends Thread {
        private String data;

        public void setData(String data) {
            this.data = data;
        }

        public void run(){
            if (socket != null && socket.isConnected()) {
                Log.d("Function_Socket_Client", "发送数据");
                try {
                    OutputStream ops = socket.getOutputStream();//得到socket输入流
                    OutputStreamWriter opsw = new OutputStreamWriter(ops);//
                    BufferedWriter writer = new BufferedWriter(opsw);
                    if (this.data != null){
                        writer.write(this.data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
                        writer.flush();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    @OnClick({R.id.btn_addFriend})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_addFriend:
                String friendAccount = edt_friendAccount.getText().toString();
                //判断是否时自己
                if(friendAccount.equals(username)){
                    Toast.makeText(this, "请不要犯如此简单的错误!!!", Toast.LENGTH_SHORT).show();
                    return;
                }

                if(isRepeatFriend(friendAccount)){
                    Toast.makeText(this, "已拥有该好友。", Toast.LENGTH_SHORT).show();
                    return;
                }
                //数据不重复,发送添加好友
                Log.d("FriendListActivity","sendAddFriendRequest");
                String data = "{cmd:addFriend}{username:"+friendAccount+"}";
                sendDataPacketThread dataPacketThread = new sendDataPacketThread();
                dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
                dataPacketThread.start();
                break;
        }
    }

    private boolean isRepeatFriend(String friendAccount) {
        SQLiteDatabase db;
        ContentValues values;
        SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
        db = helper.getReadableDatabase();
        //Cursor cursor = db.rawQuery("select * from keyInformation ",null);

        Cursor cursor = db.query("keyInformation",
                null,null,null,null,null,null);
        if (cursor.getCount() != 0 ) {
            cursor.moveToFirst();
            while (cursor.moveToNext()){
                if(cursor.getString(1).equals(friendAccount)){
                    return true;
                }
            }
        }
        return false;
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void serverResult(String serverResponse) {
        Log.i("FriendListActivity", "resultCode:" + serverResponse);
        //长度大于120,非普通包,必定是密钥
        if(serverResponse.length() > 120){
            int responseBeginIndex1=serverResponse.indexOf(":")+1;
            int responseEndIndex1 = serverResponse.indexOf("}");
            String response=serverResponse.substring(responseBeginIndex1,responseEndIndex1);
            Log.d("FriendListActivity"," bigPacketResponse: "+response);
            //密钥
            if(response.equals("sendPublicKey") ){
                int keyBeginIndex = serverResponse.indexOf(":",responseEndIndex1) + 1;
                int keyEndIndex = serverResponse.length()-1;
                String key = serverResponse.substring(keyBeginIndex,keyEndIndex);
                String friendAccount = edt_friendAccount.getText().toString();
                storeKey(key,friendAccount,SRConstantsUtil.PublicKeyType);
                showFriendList();
                //showNewFriendItem();//更新好友列表
            }
            else if(response.equals("sendPrivateKey")){
                int keyBeginIndex = serverResponse.indexOf(":",responseEndIndex1) + 1;
                int keyEndIndex = serverResponse.length()-1;
                String key = serverResponse.substring(keyBeginIndex,keyEndIndex);
                storeKey(key,username,SRConstantsUtil.PrivateKeyType);
            }

            return;
        }
        //普通包
        switch (serverResponse) {
            case SRConstantsUtil.CONNECTSUCCESS:
                Toast.makeText(FriendListActivity.this, "与服务器连接成功", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.EXISTAPPNORMAL:
                Toast.makeText(FriendListActivity.this, "退出APP", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.NOFRIENDACCOUNT:
                Toast.makeText(FriendListActivity.this, "无此好友", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.GETPUBLICKEYERROR:
                Toast.makeText(FriendListActivity.this, "获取账号公钥失败", Toast.LENGTH_SHORT).show();
                break;
                default:
                    //Toast.makeText(FriendListActivity.this, "与服务器连接失败", Toast.LENGTH_SHORT).show();
                    break;
        }
    }

    private void storeKey(String key,String account,int type) {

        SQLiteDatabase db;
        ContentValues values;
        SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
        db = helper.getWritableDatabase();
        values = new ContentValues();//创建Contentvalues对象

        //好友公钥
        if (SRConstantsUtil.PublicKeyType == type){
            Log.d("FriendListActivity","存储公钥");
            Log.d(account+"公钥:",key);
            values.put("USERNAME", account);//将数据添加到ContentValues对象
            values.put("USERKEY",key);
            db.insert("keyInformation",null,values);
            db.close();
        }
        //用户私钥
        else{
            Log.d("FriendListActivity","存储私钥");
            Log.d(account+"私钥:",key);
            values.put("USERNAME", account);//将数据添加到ContentValues对象
            //私钥加密存储
            values.put("USERKEY",AESUtil.encrypt(ConstantUtil.PRIVATEKEY_AESKey,key));
            db.insert("keyInformation",null,values);
            db.close();
        }
    }

    @Override
    public void finish() {
        Log.d("FriendListActivity","finish()");
        super.finish();

    }

    @Override
    protected void onDestroy() {
        String data = "{cmd:exitApp}"+"{username:"+username+"}";
        sendDataPacketThread dataPacketThread = new sendDataPacketThread();
        dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
        dataPacketThread.start();
        super.onDestroy();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        //String data = "{cmd:connect}{username:"+username+"}";
        //sendcthread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
        //sendcthread.start();
    }
}

Funtion_Socket_Client

package com.example.socketclient.Activity;


import android.app.ProgressDialog;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.RequiresApi;

import com.example.socketclient.R;
import com.example.socketclient.SQL.SQLiteHelper;
import com.example.socketclient.Thread.ClientSendDataThread;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MD5Util;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.RSAUtil;
import com.example.socketclient.Util.SRConstantsUtil;
import com.example.socketclient.Util.ToolUtil;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

import butterknife.BindView;
import butterknife.OnClick;


/**
 * 客户端界面
 */
public class Function_Socket_Client extends BaseEventActivity {
    @BindView(R.id.edtTxt_Content)
    EditText edtTxt_Content;
    @BindView(R.id.tv_friendAccount)
    TextView tv_friendAccount;
    @BindView(R.id.tv_clientLocalAddress)
    TextView tv_clientLocalAddress;
    @BindView(R.id.tv_clientReceivedContent)
    TextView tv_clientReceivedContent;
    @BindView(R.id.tv_clientDecryptContent)
    TextView tv_clientDecryptContent;

    private ProgressDialog mProgressDialog;//加载的小菊花
    private String serverIp=null;
    private String username = null;
    private Socket socket = null;
    private String friendAccount = null;


    /**
     * 初始化
     */
    @Override
    protected void init() {
        tv_clientLocalAddress.setText(ToolUtil.getHostIP());
        Bundle bundle = this.getIntent().getExtras();
        serverIp = bundle.getString(ConstantUtil.SERVERIP);
        username = bundle.getString(ConstantUtil.USERNAME);
        friendAccount = bundle.getString(ConstantUtil.FRIENDACCOUNT);
        tv_friendAccount.append(friendAccount);
        Log.d("Function_Socket_Client","host ip is "+serverIp+", username is "+username);

        MySocketApplication app = (MySocketApplication) getApplicationContext();
        socket = app.getSocket();
        //定义线程
        recPacketThread recpthread = new recPacketThread(socket);
        recpthread.start();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //connect();//创建时刻与服务器连接的进程
    }


    private void connect() {
        new Thread(){
            @Override
            public void run()
            {
                super.run();
                    MySocketApplication app = (MySocketApplication) getApplicationContext();
                    socket = app.getSocket();

                if (socket != null&&socket.isConnected()) {
                    Log.d("Function_Socket_Client","接受服务器信息中");
                    try {
                        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        String msg = null;
                        while(true) {
                            try {
                                if ((msg =  br.readLine())== null)break;
                                    msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                                    Log.d("Function_Socket_Client","接收到服务器数据:"+msg);
                                    EventBus.getDefault().post(msg);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    @Override
    protected int getLayoutResId() {
        return R.layout.function_socket_client;
    }


    @RequiresApi(api = Build.VERSION_CODES.O)
    @OnClick({R.id.btn_encryptAndSend})
    public void onClick(View v)  {
        switch (v.getId()) {
            case R.id.btn_encryptAndSend:
                String text = edtTxt_Content.getText().toString().trim();
                try {
                    SendMessage(text);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                break;
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void SendMessage(String text) throws Exception {
        //封装发送的消息:时间+内容
        String msg = getCurrentTime(2)+": "+text;

        String digitalSignature = MD5Util.toMD5(msg);
        msg = "{MD5ds:"+digitalSignature+"}"+msg;
        //获取好友公钥
        String publicKey = getKey(friendAccount);
        //根据公钥获取密码种子,根据密码种子生成会话密钥,用密钥生成密文。
        String keySeed = getSeedFromString(publicKey,30);
        String cipherText =AESUtil.encrypt(keySeed,msg);
        Log.d("Socket_ClientActivity","AES对称密钥加密后:"+cipherText);
        //密文加上密码种子发送出去,以:作为分隔符
        cipherText = cipherText +":"+keySeed;
        //公钥加密
        String S = RSAUtil.encrypt(cipherText,publicKey);
        Log.d("Socket_ClientActivity","RSA公钥加密后:"+S);
        String data = "{cmd:sendMsg}{receiverAccount:"+friendAccount+"}{message:"+S+"}{sender:"+username+"}";
        new ClientSendDataThread(serverIp, AESUtil.encrypt(ConstantUtil.ServerAESKey, data), ConstantUtil.port).start();//消息发送方启动线程发送消息
        showProgressDialog("尝试发送数据到\n\t\t" + friendAccount, true);
    }

    private String getSeedFromString(String publicKey,int length) {
        String seed = "";
        int keyLength = length;
        int r  = (int)(Math.random()*(publicKey.length()-keyLength));
        System.out.println("random number: "+r);
        seed = publicKey.substring(r,r+keyLength);
        System.out.println("randomKey : "+seed);
        return seed;
    }

    private String getKey(String account) {
        String key ="";
        SQLiteDatabase db;
        ContentValues values;
        SQLiteHelper helper = new SQLiteHelper(Function_Socket_Client.this,username);
        db = helper.getReadableDatabase();
        //执行查询的SQL语句
        Cursor cursor = db.rawQuery("select * from keyInformation where USERNAME=? ",
                new String[]{account});
        if (cursor.getCount() != 0 ) {
            cursor.moveToFirst();
            key =  cursor.getString(2);
        }
        cursor.close();
        db.close();
        Log.d("Function_Socket_Client","get key from sql:"+key);
        if(account.equals(username))
            return AESUtil.decrypt(ConstantUtil.PRIVATEKEY_AESKey,key);
        return key;
    }

    private class recPacketThread extends Thread {
        Socket socket;
        recPacketThread(Socket socket){
            this.socket = socket;
        }
        public void run(){
            if (socket != null && socket.isConnected()) {
                Log.d("Function_Socket_Client", "开启接收线程");
                try {
                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String msg = "";
                    while (true) {
                        try {
                            if ((msg = br.readLine()).equals("")) ;
                            else {
                                Log.d("Function_Socket_Client", "接收到服务器数据(未解密):" + msg);
                                msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                                Log.d("Function_Socket_Client", "接收到服务器数据(已解密):" + msg);
                                EventBus.getDefault().post(msg);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if(socket.isClosed())break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    /**
     * 连接结果
     *
     * @param serverResponse 0:连接超时;1:发送成功 2:失败
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void sendResult(String serverResponse) throws Exception {
        Log.i("Function_Socket_Client", "resultCode:" + serverResponse);
        dismissProgressDialog();
        //用户发送的密文数据
        if(serverResponse.length()>120){
            String M = DecryptReceiveMsg(serverResponse);
            tv_clientDecryptContent.append(M+"\n");
        }
        //常规数据
        else {
            switch (serverResponse) {
                case SRConstantsUtil.MSGSEND_SUCCESS:
                    Toast.makeText(this, "发送成功!", Toast.LENGTH_SHORT).show();
                    break;
                case SRConstantsUtil.MSGSEND_FAILED:
                    Toast.makeText(this, "发送失败,服务器无响应!", Toast.LENGTH_SHORT).show();
                    break;
                case SRConstantsUtil.MSGSEND_FAILED_NOT_EXIST:
                    Toast.makeText(this, "发送失败,接收者不存在!", Toast.LENGTH_SHORT).show();
                    break;
                case SRConstantsUtil.MSGSEND_FAILED_NOT_ONLINE:
                    Toast.makeText(this, "发送失败,接收者不在线!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    Toast.makeText(this, "接收数据格式错误!", Toast.LENGTH_SHORT).show();
                    tv_clientReceivedContent.append(serverResponse+"\n");
                    //tv_clientDecryptContent.append(AESUtil.decrypt(ConstantUtil.password, resultCode)+"\n");
                    break;

            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private String DecryptReceiveMsg(String serverResponse) throws Exception {
        int colon1BeginIndex = serverResponse.indexOf(":");
        int colon2BeginIndex = serverResponse.indexOf(":", colon1BeginIndex+1);
        int colon3BeginIndex = serverResponse.indexOf(":", colon2BeginIndex+1);

        String C =serverResponse.substring(colon3BeginIndex+1,serverResponse.length()-1);
        System.out.println("split_msg,密文: "+C);
        tv_clientReceivedContent.append(C+"\n\n");
        String privateKey = getKey(username);
        String M = RSAUtil.decrypt(C,privateKey);
        System.out.println("RSA解密后的明文: "+M);

        System.out.println("数据长度 : "+M.length());
        int colonIndex = M.indexOf(":");
        System.out.println("colonIndex长度 : "+colonIndex);
        String msg = M.substring(0,colonIndex);
        System.out.println("msg : "+msg);
        String keySeed = M.substring(colonIndex+1);
        System.out.println("keySeed:"+keySeed);

        M = AESUtil.decrypt(keySeed,msg);
        System.out.println("进行双密钥解密的明文是:"+M);

        int md5Index = M.indexOf(":")+1;
        String MD5DS = M.substring(md5Index,md5Index+32);
        System.out.println("接收到的MD5数字签名:"+MD5DS);
        int msgBeginIndex = M.indexOf("}")+1;
        String recMsg = M.substring(msgBeginIndex);
        System.out.println("接收到的消息:"+recMsg);
        if(MD5Util.toMD5(recMsg).equals(MD5DS)){
            System.out.println("数据完整.");
        }
        else {
            System.out.println("数据不完整,请确认!");
            Toast.makeText(Function_Socket_Client.this, "注意:接收的数据不完整。", Toast.LENGTH_SHORT).show();
        }

        return recMsg;
    }

    /**
     * 数据加载小菊花
     *
     * @param msg      内容
     * @param isCancel 是否允许关闭 true - 允许  false - 不允许
     */
    public void showProgressDialog(final String msg, final boolean isCancel) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (mProgressDialog == null) {
                        mProgressDialog = new ProgressDialog(Function_Socket_Client.this);
                    }
                    if (mProgressDialog.isShowing()) {
                        return;
                    }
                    mProgressDialog.setMessage(msg);
                    mProgressDialog.setCancelable(isCancel);
                    mProgressDialog.setCanceledOnTouchOutside(false);
                    mProgressDialog.setOnCancelListener(null);
                    mProgressDialog.show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 隐藏数据加载的进度小菊花
     **/
    public void dismissProgressDialog() {

        try {
            if (mProgressDialog != null && mProgressDialog.isShowing()) {
                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {
                                mProgressDialog.dismiss();
                            }
                        }
                );
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
         获取当前时间
         输入参数:type
         0 返回年月日 时分秒
         1 返回年月日
         2 返回时分秒
      */
    private String getCurrentTime(int type) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        if(type == 0)
            return simpleDateFormat.format(date);
        if(type == 1)
            return simpleDateFormat.format(date).substring(0,11);//截取第一个到第十一个字符
        else
            return simpleDateFormat.format(date).substring(12);
    }

}


LoginActivity

package com.example.socketclient.Activity;


import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.example.socketclient.R;
import com.example.socketclient.Thread.ClientSendDataThread;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.RSAUtil;
import com.example.socketclient.Util.SRConstantsUtil;
import com.example.socketclient.Util.ToolUtil;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Map;

import butterknife.BindView;
import butterknife.OnClick;

public class LoginActivity extends BaseEventActivity {
    @BindView(R.id.et_username)
    EditText et_username;
    @BindView(R.id.et_password)
    EditText et_password;
    @BindView(R.id.et_serverIp)
    TextView et_serverIp;
    private String serverIp;
    private String username= null;
    private String password = null;
    private ProgressDialog mProgressDialog;//加载的小菊花
    private String localIp;
    private Socket socket;

    @Override
    protected void init() {
        localIp = ToolUtil.getHostIP();
    }

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_login;
    }

    @OnClick({R.id.btn_regist,R.id.btn_login})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_regist:
                sendRegisterInfoToServer();
                break;
            case R.id.btn_login:
                sendLoginInfoToServer();
                break;
        }
    }

    private void sendRegisterInfoToServer(){
        username = et_username.getText().toString().trim();
        password = et_password.getText().toString().trim();
        serverIp = et_serverIp.getText().toString().trim();
        String data = "{cmd:register}{username:"+username+"}{password:"+password+"}";
        if (ToolUtil.IsIpv4(serverIp)) {
            new ClientSendDataThread(serverIp, AESUtil.encrypt(ConstantUtil.ServerAESKey, data), ConstantUtil.port).start();//消息发送方启动线程发送消息
            showProgressDialog("尝试发送数据到\n\t\t" + serverIp, true);
        } else {
            Toast.makeText(LoginActivity.this, "IP不合法!", Toast.LENGTH_SHORT).show();
        }
    }

    private void sendLoginInfoToServer() {
        username = et_username.getText().toString().trim();
        password = et_password.getText().toString().trim();
        serverIp = et_serverIp.getText().toString().trim();

        String data = "{cmd:login}{username:"+username+"}{password:"+password+"}{clientIp:"+localIp+"}";
        if (ToolUtil.IsIpv4(serverIp)) {
            socket = new Socket();
            login(socket,AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
            showProgressDialog("尝试发送数据到\n\t\t" + serverIp, true);
        } else {
            Toast.makeText(LoginActivity.this, "IP不合法!", Toast.LENGTH_SHORT).show();
        }
    }

    private void login(Socket socket, String data) {
        new Thread(){
            @Override
            public void run() {
                try {
                    socket.connect(new InetSocketAddress(serverIp,ConstantUtil.port),ConstantUtil.TIME_MILLIS);//设置超时时间
                } catch (UnknownHostException e) {
                    EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
                    Log.d("LoginActivity", "socket.connect has UnknownHostException" + e.getMessage());
                } catch (SocketTimeoutException e) {
                    EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
                    Log.d("LoginActivity", "socket.connect has TimeoutException:" + e.getMessage());
                }catch (IOException e){
                    EventBus.getDefault().post(ConstantUtil.CODE_EHOSTUNREACH);
                    Log.d("LoginActivity", "socket.connect has IOException:" + e.getMessage());
                }
                if (socket != null&&socket.isConnected()) {
                    try {
                        OutputStream ops = socket.getOutputStream();//得到socket输入流
                        OutputStreamWriter opsw = new OutputStreamWriter(ops);//
                        BufferedWriter writer = new BufferedWriter(opsw);
                        writer.write(data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
                        writer.flush();

                        //接收服务器响应
                        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        String msg = br.readLine();
                        //length<120,才是登录界面应该接收到的包,这个活动结束后 因为有阻塞,线程仍然在跑
                        if ( msg != null && msg.length()<120)
                        {
                            msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                            Log.d("LoginActivity","接收到服务器数据:"+msg);
                            EventBus.getDefault().post(msg);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    /**
     * 连接结果
     *
     * @param resultCode 0:连接超时;1:发送成功 2:失败
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void sendResult(String resultCode) {
        Log.i("LoginActiviy", "resultCode :" + resultCode);
        dismissProgressDialog();
        switch (resultCode) {
            case SRConstantsUtil.LOGIN_SUCCESS:
                //存储创建的socket
                MySocketApplication app = (MySocketApplication) getApplicationContext();
                app.setSocket(socket);

                Toast.makeText(LoginActivity.this, "登录成功。", Toast.LENGTH_SHORT).show();
                Bundle bundle = new Bundle();
                bundle.putString(ConstantUtil.SERVERIP,serverIp);
                bundle.putString(ConstantUtil.USERNAME,username);
                Intent intent = new Intent(LoginActivity.this, FriendListActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
                finish();
                break;
            case SRConstantsUtil.REGISTER_SUCCESS:
                Toast.makeText(LoginActivity.this, "注册成功!", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.REGISTER_FAILED:
                Toast.makeText(LoginActivity.this, "注册失败,已存在该用户名!", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.LOGIN_FAILED:
                Toast.makeText(LoginActivity.this, "登录失败,请检查账号和密码!", Toast.LENGTH_SHORT).show();
                break;
            case SRConstantsUtil.LOGIN_ALREADY:
                Toast.makeText(LoginActivity.this, "登录失败,该账户已经登录!", Toast.LENGTH_SHORT).show();
                break;
            case ConstantUtil.CODE_TIMEOUT:
                Toast.makeText(LoginActivity.this, "连接超时...", Toast.LENGTH_SHORT).show();
                break;
            case ConstantUtil.CODE_UNKNOWN_HOST:
                Toast.makeText(LoginActivity.this, "错误-未知的host", Toast.LENGTH_SHORT).show();
                break;
            case ConstantUtil.CODE_EHOSTUNREACH:
                Toast.makeText(LoginActivity.this, "No route to host", Toast.LENGTH_SHORT).show();
                break;

        }

    }

    /**
     * 数据加载小菊花
     *
     * @param msg      内容
     * @param isCancel 是否允许关闭 true - 允许  false - 不允许
     */
    public void showProgressDialog(final String msg, final boolean isCancel) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (mProgressDialog == null) {
                        mProgressDialog = new ProgressDialog(LoginActivity.this);
                    }
                    if (mProgressDialog.isShowing()) {
                        return;
                    }
                    mProgressDialog.setMessage(msg);
                    mProgressDialog.setCancelable(isCancel);
                    mProgressDialog.setCanceledOnTouchOutside(false);
                    mProgressDialog.setOnCancelListener(null);
                    mProgressDialog.show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    /**
     * 隐藏数据加载的进度小菊花
     **/
    public void dismissProgressDialog() {

        try {
            if (mProgressDialog != null && mProgressDialog.isShowing()) {
                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {
                                mProgressDialog.dismiss();
                            }
                        }
                );
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

SQL

这是用来存储自己添加的好友账号、公钥,自己的私钥的本地数据库。

SQLiteHepler

package com.example.socketclient.SQL;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class SQLiteHelper extends SQLiteOpenHelper {
    //构造socket的数据库
    public SQLiteHelper(Context context,String DBMame) {
        super(context, DBMame+".db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建一个名字为 keyInformation,3列的表格
        //列:1: _id,2: USERNAME,3: USERKEY
        db.execSQL("CREATE TABLE keyInformation(_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "USERNAME VARCHAR(20),USERKEY VARCHAR(2500))");//,LATESTIP VARCHAR(15)

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

Thread

ClientSendDataThread

package com.example.socketclient.Thread;



import android.util.Log;

import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;

import org.greenrobot.eventbus.EventBus;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

/**
 * 数据发送线程
 */
public class ClientSendDataThread extends Thread {
    private Socket socket;
    private String ip;//接收方的IP
    private int port;//接收方的端口号
    private String data;//准备发送的数据

    public ClientSendDataThread(String ip, String data, int port) {
        this.ip = ip;
        this.data = data;
        this.port = port;
    }

    @Override
    public void run() {
        try {
            socket = new Socket();//建立Socket(),与服务器建立连接
            socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//设置超时时间
        } catch (UnknownHostException e) {
            EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
            Log.d("ClientSendDataThread", "socket.connect has UnknownHostException" + e.getMessage());
        } catch (SocketTimeoutException e) {
            EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
            Log.d("ClientSendDataThread", "socket.connect has TimeoutException:" + e.getMessage());
        }catch (IOException e){
            EventBus.getDefault().post(ConstantUtil.CODE_EHOSTUNREACH);
            Log.d("ClientSendDataThread", "socket.connect has IOException:" + e.getMessage());
        }
        if (socket != null&&socket.isConnected()) {
            try {
                OutputStream ops = socket.getOutputStream();//得到socket输入流
                OutputStreamWriter opsw = new OutputStreamWriter(ops);//
                BufferedWriter writer = new BufferedWriter(opsw);
                writer.write(data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
                writer.flush();

                //接收服务器响应
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = br.readLine();
                if ( msg != null )
                {
                    msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                    Log.d("ClientSendDataThread","接收到服务器数据:"+msg);

                        if(msg.length()<=35)
                            EventBus.getDefault().post(msg);//发布事件

                }
                else {
                    Log.d("ClientSendDataThread","没接收到服务器响应");
                    EventBus.getDefault().post(ConstantUtil.MSGSEND_FAILED);
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

ConnectThread

package com.example.socketclient.Thread;

import android.content.Context;
import android.util.Log;

import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MySocketApplication;

import org.greenrobot.eventbus.EventBus;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

public class ConnectThread extends Thread {
    private Socket socket;
    private String ip;//接收方的IP
    private int port;//接收方的端口号
    private Context context;//准备发送的数据
    private String username;

    public ConnectThread(String ip, int port ,Context context,String username) {
        this.ip = ip;
        this.context = context;
        this.port = port;
        this.username = username;
    }
    @Override
    public void run() {
        try {
            socket = new Socket();//建立Socket(),与服务器建立连接
            socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//设置超时时间
        } catch (UnknownHostException e) {
            EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
            Log.d("ClientSendDataThread", "SendDataThread.init() has UnknownHostException" + e.getMessage());
        } catch (SocketTimeoutException e) {
            EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
            Log.d("ClientSendDataThread", "SendDataThread.init() has TimeoutException:" + e.getMessage());
        }catch (IOException e){
            Log.d("ClientSendDataThread", "SendDataThread.init() has IOException:" + e.getMessage());
        }

        if (socket != null&&socket.isConnected()) {
            try {
                OutputStream ops = null;//得到socket输入流
                try {
                    ops = socket.getOutputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                OutputStreamWriter opsw = new OutputStreamWriter(ops);//
                BufferedWriter writer = new BufferedWriter(opsw);
                String data = "{cmd:connect}{username:"+username+"}";
                writer.write(AESUtil.encrypt(ConstantUtil.ServerAESKey,data) + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
                writer.flush();

                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = null;
                while(true) {
                    try {
                        if ((msg =  br.readLine())== null)break;
                        msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
                        Log.d("Function_Socket_Client","接收到服务器数据:"+msg);
                        EventBus.getDefault().post(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Util

AESUtil

package com.example.socketclient.Util;

import android.util.Log;

import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES加密工具类
 */
public class AESUtil {

    //    private static final String CipherMode = "AES/ECB/PKCS5Padding";使用ECB加密,不需要设置IV,但是不安全
    private static final String CipherMode = "AES/CFB/NoPadding";//使用CFB加密,需要设置IV;CFB(Cipher FeedBack Mode,加密反馈),不填充

    /**
     * 生成加密后的密钥
     *
     * @param keySeed 密钥种子
     * @return isSucceed
     */
    public static SecretKeySpec createKey(String keySeed) {
        byte[] data = null;
        if (keySeed == null) {
            keySeed = "";
        }
        StringBuilder sb = new StringBuilder(32);//32位容量的字符串
        sb.append(keySeed);//字符串追加密码
        while (sb.length() < 32) {
            sb.append("0");//少于32位,追加‘0’
        }
        if (sb.length() > 32) {
            //setLength(newLength)
            //如果 newLength 参数小于当前长度,则长度将更改为指定的长度。
            //如果 newLength 参数大于或等于当前长度,则将追加有效的 null 字符 ('u0000'),使长度满足 newLength 参数。
            sb.setLength(32);
        }

        try {
            data = sb.toString().getBytes("UTF-8");//得到 使用UTF-8编码表 的一个系统默认的编码格式的 字节数组
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return new SecretKeySpec(data, "AES");//根据32字节的数据,生成一个AES算法生成的密钥
    }

    // /** 加密字节数据 **/
    private static byte[] encrypt(byte[] content, String password) {
        try {
            SecretKeySpec key = createKey(password);//根据密钥种子生成密钥
            System.out.println(key);
            Cipher cipher = Cipher.getInstance(CipherMode);

            //初始化Cipher,mode指定是加密还是解密,key为公钥或密钥;ENCRYPT_MODE加密模式,
            // 实例化IvParameterSpec对象,使用指定的初始化向量
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(
                    new byte[cipher.getBlockSize()]));


            //使用CFB加密CFB(Cipher FeedBack Mode,加密反馈)、不填充的方式
            //init为 加密形式
            //返回btye[]数组
            return cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // /** 加密(结果为16进制字符串) **/
    public static String encrypt(String password, String content) {
        //Log.d("加密前", "seed=" + password + "\ncontent=" + content);
        byte[] data = null;//byte字节数组
        try {
            data = content.getBytes("UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        data = encrypt(data, password);//加密字节数据
        String result = byte2hex(data);//字节转hex
        Log.d("AES加密后", "result=" + result);
        return result;
    }

    // /** 解密字节数组 **/
    private static byte[] decrypt(byte[] content, String password) {

        try {
            SecretKeySpec key = createKey(password);
            Cipher cipher = Cipher.getInstance(CipherMode);
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(
                    new byte[cipher.getBlockSize()]));

            return cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // /** 解密16进制的字符串为字符串 **/
    public static String decrypt(String password, String content) {
        Log.d("AES解密前", "seed=" + password + "\ncontent=" + content);
        byte[] data = null;
        try {
            data = hex2byte(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        data = decrypt(data, password);
        if (data == null)
            return null;
        String result = null;
        try {
            result = new String(data, "UTF-8");
            //Log.d("解密后", "result=" + result);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    // /** 字节数组转成16进制字符串 **/
    private static String byte2hex(byte[] b) { // 一个字节的数,
        StringBuilder sb = new StringBuilder(b.length * 2);
        String tmp ;
        for (byte aB : b) {
            // 整数转成十六进制表示
            tmp = (Integer.toHexString(aB & 0XFF));
            if (tmp.length() == 1) {
                sb.append("0");
            }
            sb.append(tmp);
        }
        return sb.toString().toUpperCase(); // 转成大写
    }

    // /** 将hex字符串转换成字节数组 **/
    private static byte[] hex2byte(String inputString) {
        if (inputString == null || inputString.length() < 2) {
            return new byte[0];
        }
        inputString = inputString.toLowerCase();
        int l = inputString.length() / 2;
        byte[] result = new byte[l];
        for (int i = 0; i < l; ++i) {
            String tmp = inputString.substring(2 * i, 2 * i + 2);
            result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
        }
        return result;
    }
}

ConstantUtil

package com.example.socketclient.Util;

/**
 * 常量类
 */
public class ConstantUtil {
    public static final int TIME_MILLIS = 5 * 1000;//连接超时时间
    public static final int port = 25256;//端口号
    public static final String ServerAESKey = "123456885";//加密所使用的密钥
    public static final String PRIVATEKEY_AESKey = "25764i8356";//加密所使用的密钥
    public static final String CODE_TIMEOUT = "pzl0";//连接超时
    public static final String CODE_EHOSTUNREACH = "pzl1";//No route to host
    public static final String CODE_UNKNOWN_HOST = "pzl2";//错误-未知的host


    public static final String SERVERIP = "SERVERIP";//服务器地址
    public static final String USERNAME = "USERNAME";//服务器地址

    public static final String MSGSEND_FAILED = "send_msg_failed";//连接成功


    public static final String FRIENDACCOUNT = "friendAccount";//接收者错误

}

ListItemAdapterUtil

package com.example.socketclient.Util;


import android.content.Context;
import android.widget.SimpleAdapter;

import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class ListItemAdapterUtil {
    private static List<Map<String,Object>> listItem = new ArrayList<Map<String,Object>>();
    private Context context;
    private static Map<String,Object> map = new HashMap<String,Object>();
    private static SimpleAdapter adapter;
    private int R_layout_item;
    private int[] R_layout_item_title_Array;

    /**
     * @param context 上下文
     * @param from 重map中什么键值提取键值。
     * @param R_layout_item  item.xml文件
     * @param R_layout_item_title_Array item中需要填重的控件。这里已经写死了,new String[]{"account"}
     * @return
     */
    public ListItemAdapterUtil(Context context, String[] from, @LayoutRes int R_layout_item, @IdRes int[] R_layout_item_title_Array){
        this.context = context;
        this.R_layout_item = R_layout_item;
        this.R_layout_item_title_Array = R_layout_item_title_Array;
        //map.put("account",username);
        //listItem.add(map);
        adapter = new SimpleAdapter(context,listItem,R_layout_item,
                from,R_layout_item_title_Array);
        adapter.notifyDataSetChanged();
    }

    public SimpleAdapter addItem(String newFriendAccount)
    {
        //通过不断的填充新的Map添加数据
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("account",newFriendAccount);
        listItem.add(map);
        adapter = new SimpleAdapter(context,listItem,R_layout_item,
                new String[]{"account"},R_layout_item_title_Array);
        adapter.notifyDataSetChanged();
        return adapter;
    }

    public void deleteItem()
    {
        int size = listItem.size();
        /*
        //删除一项
        if( size > 0 )
        {
            listItem.remove(listItem.size() - 1);
            adapter.notifyDataSetChanged();
        }
         */
        if(size>0)
            while( size-- > 0 )
            {
                listItem.remove(listItem.size() - 1);
                adapter.notifyDataSetChanged();
            }
    }

    public static SimpleAdapter getAdapter() {
        return adapter;
    }


}

MD5Util

package com.example.socketclient.Util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {
    //MD5加密
    public static String toMD5(String str) {
        //实例化一个指定摘要算法为MD5的MessageDigest对象
        MessageDigest algorithm = null;
        try {
            algorithm = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 重置摘要以供再次使用
        algorithm.reset();
        // 使用bytes更新摘要
        algorithm.update(str.getBytes());
        // 使用指定的byte数组对摘要进行最后更新,然后完成摘要计算
        return toHexString(algorithm.digest(), "");
    }

    private static String toHexString(byte[] digest, String string) {
        // TODO Auto-generated method stub
        StringBuilder hexString = new StringBuilder();
        for(byte b:digest) {
            String hex = Integer.toHexString(0xff & b);//toHexString传的参数应该是int类型32位,将前24位字符省略,将后8位保留。
            if (hex.length() == 1) {
                hexString.append("0");
            }
            hexString.append(hex).append(string);
        }
        return hexString.toString();
    }
}

MySocketApplication

package com.example.socketclient.Util;

import android.app.Application;
import android.widget.Toast;

import java.net.Socket;

public class MySocketApplication extends Application {
    private static Socket socket = null;
    public void setSocket(Socket socket){
        this.socket = socket;
    }
    public Socket getSocket(){
        return this.socket;
    }

}

RSAUtil

package com.example.socketclient.Util;
import android.os.Build;

import androidx.annotation.RequiresApi;

import java.util.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
    /**
     * 密钥长度 于原文长度对应 以及越长速度越慢
     */
    private final static int KEY_SIZE = 1536;

    private static final String CipherMode = "RSA";
    /**
     * 用于封装随机产生的公钥与私钥
     */
    private static Map<String, String> keyMap = new HashMap<String, String>();
    //公钥
    public static final String PUBLIC_KEY = "RSAPublicKey";
    //私钥
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 随机生成密钥对
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void genKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(CipherMode);
        // 初始化密钥对生成器
        keyPairGen.initialize(KEY_SIZE, new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        // 得到私钥字符串
        String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        // 将公钥和私钥保存到Map
        //0表示公钥
        keyMap.put(PUBLIC_KEY, publicKeyString);
        //1表示私钥
        keyMap.put(PRIVATE_KEY, privateKeyString);
    }
    public static String getPublicKey(){
        return keyMap.get(PUBLIC_KEY);
    }
    public static String getPrivateKey(){
        return keyMap.get(PRIVATE_KEY);
    }

    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static String encrypt(String str, String publicKey) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.getDecoder().decode(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(CipherMode).generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
        return outStr;
    }

    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static String decrypt(String str, String privateKey) throws Exception {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.getDecoder().decode(str);
        //base64编码的私钥
        byte[] decoded = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(CipherMode).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        String outStr = new String(cipher.doFinal(inputByte));
        return outStr;
    }
}

SRConstantUtil

package com.example.socketclient.Util;
import android.os.Build;

import androidx.annotation.RequiresApi;

import java.util.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
    /**
     * 密钥长度 于原文长度对应 以及越长速度越慢
     */
    private final static int KEY_SIZE = 1536;

    private static final String CipherMode = "RSA";
    /**
     * 用于封装随机产生的公钥与私钥
     */
    private static Map<String, String> keyMap = new HashMap<String, String>();
    //公钥
    public static final String PUBLIC_KEY = "RSAPublicKey";
    //私钥
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * 随机生成密钥对
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void genKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(CipherMode);
        // 初始化密钥对生成器
        keyPairGen.initialize(KEY_SIZE, new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        // 得到私钥字符串
        String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        // 将公钥和私钥保存到Map
        //0表示公钥
        keyMap.put(PUBLIC_KEY, publicKeyString);
        //1表示私钥
        keyMap.put(PRIVATE_KEY, privateKeyString);
    }
    public static String getPublicKey(){
        return keyMap.get(PUBLIC_KEY);
    }
    public static String getPrivateKey(){
        return keyMap.get(PRIVATE_KEY);
    }

    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static String encrypt(String str, String publicKey) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.getDecoder().decode(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(CipherMode).generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
        return outStr;
    }

    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static String decrypt(String str, String privateKey) throws Exception {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.getDecoder().decode(str);
        //base64编码的私钥
        byte[] decoded = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(CipherMode).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(CipherMode);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        String outStr = new String(cipher.doFinal(inputByte));
        return outStr;
    }
}

ToolUtil

package com.example.socketclient.Util;


import android.util.Log;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;


/**
 * 工具类
 */
public class ToolUtil {
    /**
     * 获取ip地址
     * 如果是移动网络,会显示自己的公网IP,如果是局域网,会显示局域网IP
     * 因此本例中服务器端需要断开移动网络以得到本机局域网IP
     */
    public static String getHostIP() {

        String hostIp = null;
        try {
            Enumeration nis = NetworkInterface.getNetworkInterfaces();
            InetAddress ia;
            if(nis!=null)
            while (nis.hasMoreElements()) {
                NetworkInterface ni = (NetworkInterface) nis.nextElement();
                Enumeration<InetAddress> ias = ni.getInetAddresses();
                while (ias.hasMoreElements()) {
                    ia = ias.nextElement();
                    if (ia instanceof Inet6Address) {
                        continue;// skip ipv6
                    }
                    String ip = ia.getHostAddress();
                    if (!"127.0.0.1".equals(ip)) {
                        hostIp = ia.getHostAddress();
                        break;
                    }
                }
            }
        } catch (SocketException e) {
            Log.i("error", "SocketException");
            e.printStackTrace();
        }
        return hostIp;

    }

    /**
     * 判断地址是否为IPV4地址
     */
    public static boolean IsIpv4(String ipv4) {
        if (ipv4 == null || ipv4.length() == 0) {
            return false;//字符串为空或者空串
        }
        String[] parts = ipv4.split("\\.");//因为java doc里已经说明, split的参数是reg, 即正则表达式, 如果用"|"分割, 则需使用"\\|"
        if (parts.length != 4) {
            return false;//分割开的数组根本就不是4个数字
        }
        for (String part : parts) {
            try {
                int n = Integer.parseInt(part);
                if (n < 0 || n > 255) {
                    return false;//数字不在正确范围内
                }
            } catch (NumberFormatException e) {
                return false;//转换数字不正确
            }
        }
        return true;
    }
}


layout

activity_friend_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@mipmap/bg4">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:padding="5dp"
        android:layout_marginLeft="10dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="好友账号: "
            android:textColor="#000"
            android:textSize="18sp"/>

        <EditText
            android:id="@+id/edt_friendAccount"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#2fff"
            android:padding="10dp"
            android:text="123"
            android:inputType="number"
            android:hint="请输入账号,账号只能是阿拉伯数字"/>
    </LinearLayout>
    <Button
        android:id="@+id/btn_addFriend"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:text="添加好友"
        android:gravity="center"
        android:textSize="20sp"
        android:background="#8fec"
        android:layout_margin="10dp"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="55dp">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:textSize="30dp"
            android:text="网络加密-好友列表" />
    </RelativeLayout>

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"/>

</LinearLayout>

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@mipmap/bg2"
    >
    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.5"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2.5"
        android:orientation="vertical"
        android:padding="5dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:padding="5dp"
            android:gravity="center">
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1.5"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:gravity="center"
                android:text="服务器ip:"
                android:textColor="#000"
                android:textSize="18sp"/>
            <EditText
                android:id="@+id/et_serverIp"
                android:layout_width="0dp"
                android:layout_weight="3"
                android:layout_height="wrap_content"
                android:hint="请输入服务器ip"
                android:textColorHint="#000"
                android:textColor="#000"
                android:text="192.168.1.10"
                android:inputType="numberDecimal"
                />
            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="0.5"/>




        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:padding="5dp"
            android:gravity="center">
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1.5"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:gravity="center"
                android:text="账   号:"
                android:textColor="#000"
                android:textSize="18sp"/>
            <EditText
                android:id="@+id/et_username"
                android:layout_width="0dp"
                android:layout_weight="3"
                android:layout_height="wrap_content"
                android:hint="请输入账号"
                android:textColorHint="#666"
                android:textColor="#000"
                android:text="123"
                android:inputType="number"
                />
            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="0.5"/>




        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:padding="5dp"
            android:gravity="center">
            <TextView
                android:layout_width="0dp"
                android:layout_weight="1.5"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:gravity="center"
                android:text="密   码:"
                android:textColor="#000"
                android:textSize="18sp"/>
            <EditText
                android:id="@+id/et_password"
                android:layout_width="0dp"
                android:layout_weight="3"
                android:layout_height="wrap_content"
                android:hint="请输入密码,最大长度为15"
                android:text="123"
                android:maxLength="15"
                android:textColorHint="#666"
                android:inputType="textPassword"
                android:textColor="#000"
                />
            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="0.5"/>




        </LinearLayout>



        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp">

            <Button
                android:id="@+id/btn_login"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:text="登         录"
                android:textSize="20dp"
                android:textColor="#000"
                android:background="#3000"/>
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp">

            <Button
                android:id="@+id/btn_regist"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:text="注         册"
                android:textSize="20dp"
                android:textColor="#000"
                android:background="#3000"/>
        </LinearLayout>

    </LinearLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.5"
        android:gravity="center"
        android:textSize="18sp"
        android:text="Power By 琅邪"/>
</LinearLayout>

friend_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/friend_item_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:padding="10dp"
        android:maxHeight="50dp"
        android:maxWidth="50dp"
        android:src="@drawable/item_img"/>
    <TextView
        android:id="@+id/friend_item_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_gravity="center" />

</LinearLayout>

function_socket_client.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@mipmap/bgchat">

    <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="55dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:textSize="30dp"
            android:text="网络加密-客户端" />
    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="本机地址:"
            android:textSize="18sp"
            android:textColor="#000"/>

        <TextView
            android:id="@+id/tv_clientLocalAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="true" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp">
        <TextView
            android:id="@+id/tv_friendAccount"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="好友账号:"
            android:textSize="18sp"
            android:textColor="#000"/>

    </LinearLayout>


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="55dp"
    android:padding="5dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="文本内容:"
        android:textColor="#000"
        android:textSize="18sp"/>

    <EditText
        android:id="@+id/edtTxt_Content"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:background="#4fcd"
        android:padding="10dp"
        android:text="123木头人" />
</LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="接收到的密文:"
                android:textColor="#000"
                android:textSize="18sp"/>

            <TextView
                android:id="@+id/tv_clientReceivedContent"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text=""
                android:padding="10dp"
                android:textSize="18sp"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="解密后的明文:"
                android:textColor="#000"
                android:textSize="18sp"/>

            <TextView
                android:id="@+id/tv_clientDecryptContent"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="10dp"
                android:textSize="18sp"/>
        </LinearLayout>

    </ScrollView>

    <Button
        android:id="@+id/btn_encryptAndSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="加密并发送"
        android:background="#3000"/>


</LinearLayout>

values

styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.socketclient">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:name=".Util.MySocketApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Activity.FriendListActivity"
            >
        </activity>
        <activity
            android:name=".Activity.Function_Socket_Client"
            android:windowSoftInputMode="adjustNothing|stateHidden" />
        <activity
            android:name=".Activity.LoginActivity"
            android:windowSoftInputMode="adjustNothing|stateHidden">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

build.gradle(project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
        classpath 'com.jakewharton:butterknife-gradle-plugin:10.0.0'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

build.gradle(module)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.socketclient"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.jakewharton:butterknife:10.0.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
    implementation 'org.greenrobot:greendao:3.0.1'
    implementation 'org.greenrobot:greendao-generator:3.0.0'
    implementation 'org.greenrobot:eventbus:3.0.0'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值