1. 程式人生 > >Android Java_WebSocket實現與後臺聊天通訊

Android Java_WebSocket實現與後臺聊天通訊

轉載請帶上原著連線哦~~ 

作者:Obj_class

弄了2天做出來的聊天,真的是心痛,也沒什麼難得東西,主要還是不熟悉,就會出錯,很尷尬!

先放鬆一下:來段舞蹈吧!


WebSocket簡介

WebSocket協議是基於TCP的一種新的網路協議。它實現了瀏覽器與伺服器全雙工(full-duplex)通訊——允許伺服器主動傳送資訊給客戶端。

WebSocket通訊協議於2011年被IETF定為標準RFC 6455,並被RFC7936所補充規範。

話不多說,先上圖:

不過圖片的話也沒有多少張,簡單聊天而已嘛

對了,程式碼寫的有點粗糙,咱就這水平,不喜勿噴啊

這次儘量發全點大家可以直接使用,是我所希望的

這是採用recycleview繪製的聊天介面:

我們傳送文字傳遞到後臺,後臺會收到我們穿的json去進行解析,進行記錄

好了圖片也就這樣了,沒什麼毛病,後續我們可以自行新增動態效果。

ok進入正題,走著您呢~

主介面如下:

兩張.9圖 放在mipmap的xhdpi中,雖然會報紅,但是沒關係,繼續用就可以了

在Drawable圖片會被放大的

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F7F6F6"
    tools:context="com.lgoutech.chatuidesigndemo.MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="44dp"
        android:gravity="center_vertical"
        android:background="#00A9FF"
        android:layout_alignParentTop="true"
        android:id="@+id/tab_top"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="客戶"
            android:textSize="18sp"
            android:layout_centerInParent="true"
            android:textColor="#FFF"
            />
    </RelativeLayout>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/chat_recy"
        android:layout_below="@+id/tab_top"
        android:layout_above="@+id/chat_lay"
        >

    </android.support.v7.widget.RecyclerView>


    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#B2B2B2"
        android:layout_above="@+id/chat_lay"
        />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:background="#F9F9F9"
        android:gravity="center_vertical"
        android:padding="3dp"
        android:id="@+id/chat_lay"
        >
        <ImageView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:src="@mipmap/icon_chat_add"
            android:layout_weight="1"
            android:id="@+id/img_add"
            />
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="8"
            android:background="@drawable/chat_edit_bg"
            android:layout_margin="6dp"
            android:gravity="center"
            >
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@null"
                android:padding="3dp"
                android:textSize="14sp"
                android:id="@+id/edit_context"
                android:maxLines="3"
                />
        </LinearLayout>

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="傳送"
            android:layout_weight="1.5"
            android:textSize="18sp"
            android:gravity="center"
            android:id="@+id/txt_send"
            android:textColor="@color/text_send_press"
            />
    </LinearLayout>

</RelativeLayout>

記得新增網路許可權哦!!!

所需要用的第三方框架

 compile 'com.android.support:recyclerview-v7:26.1.0'
    compile "org.java-websocket:Java-WebSocket:1.3.6"
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'

主介面MainActivity: 已經做了相關的註釋

public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private static final int STATUS_MESSAGE = 0x00;
    private RecyclerView chatRecy;
    private ContextMsg msg;
    private List<ContextMsg> msgList;
    private ChatRecyAdapter chatRecyAdapter;
    private EditText editContext;
    private TextView txtSend;
//大家自己找吧,我用的內網,大家也用不了的,哈哈
    private static final String url = "ws://服務端地址:埠97/";
    private URI uri;
    JWebSClient client;

    private Handler mhandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            String mess = (String) msg.obj;
            switch (msg.what) {
                case STATUS_MESSAGE:
                    setListSent(mess, ContextMsg.TYPE_RECEIVED);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initSocketClient();
        initView();
        initCharRecy();

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
//activity 銷燬時 關閉連線
        closeConnect();
    }

    private void initView() {
        chatRecy = findViewById(R.id.chat_recy);
        editContext = findViewById(R.id.edit_context);
        txtSend = findViewById(R.id.txt_send);

        txtSend.setOnClickListener(this);

        chatRecy.setOnTouchListener(this);


//這裡是對傳送按鈕進行了處理,使用者體驗嗎 ,你們懂得
        editContext.addTextChangedListener(new MyTxtSendWatcher());
    }

    private void initCharRecy() {
        chatRecyAdapter = new ChatRecyAdapter(MainActivity.this, getList());
        chatRecy.setLayoutManager(new LinearLayoutManager(this));
        chatRecy.setAdapter(chatRecyAdapter);
    }
//模擬資料
    private List<ContextMsg> getList() {
        msgList = new ArrayList<>();
        msgList.add(new ContextMsg("你好", ContextMsg.TYPE_RECEIVED));
        msgList.add(new ContextMsg("hello", ContextMsg.TYPE_SENT));
        msgList.add(new ContextMsg("見到你很高興", ContextMsg.TYPE_RECEIVED));
        msgList.add(new ContextMsg("me to", ContextMsg.TYPE_SENT));
        return msgList;
    }
//這裡設定文字顯示在recycle
    private void setListSent(String send, int type) {
        if (type == ContextMsg.TYPE_SENT) {
            msgList.add(new ContextMsg(send, ContextMsg.TYPE_SENT));
            editContext.setText("");
        } else {
            msgList.add(new ContextMsg(send, ContextMsg.TYPE_RECEIVED));
        }
        chatRecyAdapter = new ChatRecyAdapter(MainActivity.this, msgList);
        chatRecyAdapter.notifyDataSetChanged();
        chatRecy.scrollToPosition(chatRecyAdapter.getItemCount() - 1);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.txt_send:
                String editText = editContext.getText().toString();
                setListSent(editText, ContextMsg.TYPE_SENT);
                sendJsonObject(editText);
                break;

        }
    }


    private void senJsonInit() {
        try {
            JSONObject msg = new JSONObject();
            msg.put("from", 2);

            JSONObject init = new JSONObject();
            init.put("type", "init");
            init.put("msg", msg);

            sendMsg(init.toString());
            Log.e("init", "Init初始化成功");
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void sendJsonObject(String context) {
        try {
            JSONObject msg = new JSONObject();
            msg.put("to", 1);
            msg.put("content", context);
            msg.put("from", 2);
            msg.put("headimg", "");

            JSONObject toOne = new JSONObject();
            toOne.put("type", "msg");
            toOne.put("msg", msg);
            sendMsg(toOne.toString());

        } catch (JSONException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (v.getId()) {
            case R.id.chat_recy:
//在我們的recycleview中 觸控到後 會隱藏輸入法鍵盤的
                hideSoftInput(MainActivity.this, v);
                break;

        }
        return false;
    }

//顯示輸入法
    public static void showSoftInput(Context context, View view) {
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.showSoftInput(view, InputMethodManager.SHOW_FORCED);
    }
//隱藏輸入法
    public static void hideSoftInput(Context context, View view) {
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);

    }

//開啟socket連線
    private void initSocketClient() {
        uri = URI.create(url);
        client = new JWebSClient(uri) {
            @Override
            public void onMessage(String message) {
                Log.e("onMessage", message);
                ReceivedMsg receivedMsg = GsonManager.getGson(message, ReceivedMsg.class);
                Message msg = new Message();
                msg.what = STATUS_MESSAGE;
                msg.obj = receivedMsg.getMsg().getContent();
                mhandler.sendMessage(msg);
            }

        };
        connect();



    }

    //連線
    private void connect() {
        new Thread() {
            @Override
            public void run() {
                try {
                    client.connectBlocking();
                    Log.e("connectBlocking", "連線成功");

                    if(client.isOpen()){
                        senJsonInit();
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }

    //斷開連線
    private void closeConnect() {
        try {
            if (null != client) {
                client.close();

            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("Socket", "斷開連線異常");
        } finally {
            client = null;
        }
    }

    //傳送訊息

    /**
     *
     */
    private void sendMsg(String msg) {
        if (null != client) {
            client.send(msg);
            Log.e("傳送的訊息", msg);
        }
    }

    private class MyTxtSendWatcher implements TextWatcher{

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            if(TextUtils.isEmpty(editContext.getText())){
                txtSend.setClickable(false);
                txtSend.setTextColor(getResources().getColor(R.color.text_send_press));
            }else {
                txtSend.setClickable(true);
                txtSend.setTextColor(getResources().getColor(R.color.text_send_normal));
            }
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    }
}

接下來使我們的websocket,因為我們使用的是java_websocket所以使用方法如下:

public class JWebSClient extends WebSocketClient {


    public JWebSClient(URI serverUri) {
        super(serverUri,new Draft_6455());
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        Log.e("JWebSClient", "連線開啟onOpen");
    }

    @Override
    public void onMessage(String message) {
        Log.e("JWebSClient", message);

    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.e("JWebSClient", "關閉 斷開連線onClose");
    }

    @Override
    public void onError(Exception ex) {
        Log.e("JWebSClient", "錯誤 onError");
    }

}
很簡單。也有了註釋的,在mainActivity中使用的

對於recycleview的anapter:

public class ChatRecyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<ContextMsg> mList;
    private Context mContext;
    private LayoutInflater inflater;
    public ChatRecyAdapter(Context context, List<ContextMsg> list) {
        mList = list;
        mContext = context;
        inflater = LayoutInflater.from(context);
    }

    //onCreateViewHolder()用於建立ViewHolder例項
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        if (viewType == ContextMsg.TYPE_SENT) {
            View view = inflater.inflate(R.layout.item_right_chat, parent, false);
            RightHolder rightHolder = new RightHolder(view);
            return rightHolder;
        }else if (viewType == ContextMsg.TYPE_RECEIVED){
            View view = inflater.inflate(R.layout.item_left_chat,parent,false);
            LeftHolder leftHolder = new LeftHolder(view);
            return leftHolder;
        }

        return null;
    }

    //onBindViewHolder()用於對RecyclerView子項的資料進行賦值,會在每個子項被滾動到螢幕內的時候執行
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if (holder instanceof RightHolder) {
            RightHolder rightHolder = (RightHolder) holder;
            if (mList != null) {
                rightHolder.tvChatContent.setText(mList.get(position).getContent());
                rightHolder.imgChatHead.setImageResource(R.mipmap.head_img);

            }
        }else if (holder instanceof LeftHolder){
            LeftHolder leftHolder = (LeftHolder) holder;
            if (mList!=null){
                leftHolder.tvChatContent.setText(mList.get(position).getContent());
                leftHolder.imgChatHead.setImageResource(R.mipmap.head_img);
            }
        }

    }

    @Override
    public int getItemCount() {
        if (mList!=null)
            return mList.size();
        else
            return 0;
    }

    @Override
    public int getItemViewType(int position) {
        return mList.get(position).getType();
    }
//左佈局
    public class LeftHolder extends RecyclerView.ViewHolder {
        private TextView tvChatContent;
        private ImageView imgChatHead;
        public LeftHolder(View itemView) {
            super(itemView);
            tvChatContent =  itemView.findViewById(R.id.chat_content_text);
            imgChatHead =  itemView.findViewById(R.id.chat_item_header);
        }
    }
//右佈局
    public class RightHolder extends RecyclerView.ViewHolder {
        private TextView tvChatContent;
        private  ImageView imgChatHead;
        public RightHolder(View itemView) {
            super(itemView);
            tvChatContent =  itemView.findViewById(R.id.chat_content_text);
            imgChatHead =  itemView.findViewById(R.id.chat_item_header);
        }
    }

}

看到這裡有人問了,兩個佈局,一樣為什麼要這麼寫呢?

為什麼,我哪知道~

寫別的方式,holder找不到你的控制元件,怎麼辦,有大神可以給一下解決方式嗎?謝謝了

接下來就是從後臺傳遞過來的資料,實體:

public class ReceivedMsg {

    private String type;
    private ContextMsg msg;

    public void setType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public void setMsg(ContextMsg msg) {
        this.msg = msg;
    }

    public ContextMsg getMsg() {
        return msg;
    }


    public class Msg {

        private int to;
        private String content;
        private int from;
        private String headimg;
        public void setTo(int to) {
            this.to = to;
        }
        public int getTo() {
            return to;
        }

        public void setContent(String content) {
            this.content = content;
        }
        public String getContent() {
            return content;
        }

        public void setFrom(int from) {
            this.from = from;
        }
        public int getFrom() {
            return from;
        }

        public void setHeadimg(String headimg) {
            this.headimg = headimg;
        }
        public String getHeadimg() {
            return headimg;
        }

    }
}

Gson工具解析json資料,已封裝,使用方法就在程式碼中

public class GsonManager<T>{

    public static <T>T  getGson(String json,Class<T> tClass){
        Gson gson=new Gson();
        return gson.fromJson(json,tClass);
    }

    public static <T> String mapToJson(Map<String, T> map) {
        Gson gson = new Gson();
        String jsonStr = gson.toJson(map);
        return jsonStr;
    }
}
大家根據自己需要,適當修改即可!
注意:
 client.connectBlocking();
                    Log.e("connectBlocking", "連線成功");

                    if(client.isOpen()){
                        senJsonInit();
                    }

跟大家提一下,這裡用到了connectBlocking 而沒有用connect為什麼呢?

1.因為後臺的原因,我是需要在連線成功後傳遞一下我是哪位使用者的,所以在連線成功後我會想後臺傳送一個json

2.所以這裡Blocking 會多出一個等待操作,然後我判斷了連線打沒開啟,進行傳送資料,否則會報錯的哦,

3.錯誤原因在於未連線就傳送資料,這當然會報錯。。。。。

public boolean connectBlocking() throws InterruptedException {
		connect();
		connectLatch.await();
		return engine.isOpen();
	}



感謝各位!請認準中國馳名品牌哦~
謝謝各位老鐵支援~