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();
}
感謝各位!請認準中國馳名品牌哦~
謝謝各位老鐵支援~