1. 程式人生 > >Android多人聊天室小專案

Android多人聊天室小專案

多人聊天室小專案筆記(APP客戶端)

這兩天自己寫了一個多人聊天室,伺服器是用Java寫的,爛的一批,不想寫了,但是安卓客戶端上遇到了很多問題還是有必要說一下。

1、功能

這個APP客戶端是基於我自己寫的伺服器來執行的,不過有需要的夥伴可以根據自己的需要進行一些更改,可以把它做成一個手機套接字工具。
主要功能:通過套接字進行遠端通訊(非同步收發)。
附加功能:每次登陸可以為自己添加個性暱稱,通過對伺服器發來的訊息進行不同的操作。

2、實現

有了功能,我們就來實現它。

主要通訊功能(寫)

因為是客戶端,所以我們這裡只需要使用一個套接字連線伺服器就可以了,下面是程式碼

package com.example.a9042.socket;

import android.view.View;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

import com.example.a9042.chatclint.MainWindow;
/*這個類用來進行套接字操作,繼承父類Thread*/
public class MyScoket
extends Thread{
private Socket mySocket;//主要套接字 private String ip="118.24.107.35";//目標伺服器IP private int port=11111;//目標伺服器埠 private MainWindow mw;//主視窗例項,用來實現主類的一些方法 private DataOutputStream pw;//輸出流,用來發送訊息 private Receive rec;//接收類的例項化物件,主要用來啟動接收伺服器訊息執行緒 /*構造方法,傳遞主視窗例項*/ public MyScoket
(MainWindow w){ this.mw=w; } /*子執行緒run方法*/ public void run(){ if(mySocket==null){ //mw.showToast("正在連線伺服器"); while(true){ try { mySocket=new Socket(ip,port);//通過Socket構造器將伺服器IP和埠繫結到套接字 /*如果套接字連線上了*/ if(mySocket.isConnected()){ mw.showToast("登陸成功");//通過彈出訊息提示登陸伺服器成功 rec=new Receive(this.mySocket,mw);//通過receive類構造器將套接字和主視窗傳遞給rec物件 rec.start();//啟動接收伺服器訊息執行緒 pw=new DataOutputStream(mySocket.getOutputStream());//繫結輸出流 break; } } catch (IOException e) { //mw.showToast("連線伺服器失敗"); mw.heart(mySocket); } } } } /*向伺服器傳送訊息的方法*/ public void send(final String str){ new Thread(){ @Override public void run(){ try { pw.writeUTF(mw.name+":"+str); } catch (IOException e) { e.printStackTrace(); } } }.start(); } }

這裡,其實第40行的heart()方法也應該放到MySocket類裡面的,可是因為這個方法在其他類中也要使用,就把這個方法放在了主類中。
這個heart()方法很重要,因為對於安卓來說,因為智慧手機使用的是移動無線網路,所以我們必須使用一種方法每間隔一段時間就要確定一下客戶端和伺服器是否連線。這個方法就是”心跳機制“。

心跳機制

心跳機制是保證安卓客戶端與伺服器通過套接字保持長連線的一種機制,大概意思是:客戶端每間隔一段時間向伺服器傳送一條資訊,伺服器收到後會反饋一個資訊給客戶端。進行了這麼一個操作以後,客戶端就可以與伺服器保持長連結了。

主要通訊功能(讀)

非同步收發訊息,其實就是利用一個子執行緒來讀取訊息,用另一個執行緒來發送訊息。傳送訊息在上面的程式碼已經實現了,下面是讀取訊息的程式碼:

package com.example.a9042.socket;

import com.example.a9042.chatclint.MainWindow;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/*這個類是用來接收伺服器訊息的*/
public class Receive extends Thread{
    private DataInputStream br;//輸入流
    private MainWindow mw;//獲取主視窗例項
    private Socket so;//獲取套接字例項

    /*構造方法,主要是用來傳遞套接字和主視窗*/
    public Receive(Socket s, MainWindow w){
        mw=w;
        so=s;
    }
    /*子執行緒執行接收方法*/
    public void run(){
        try {
            String str;//用來儲存接收到的資訊
            br=new DataInputStream(so.getInputStream());//將輸入流繫結為套接字輸入流
            while (true) {//子執行緒持續接收伺服器發來的訊息
                str = br.readUTF();//讀取伺服器發來的訊息
                mw.getMessage(str);//將伺服器發來的訊息填充到TextView
            }
        } catch (IOException e) {//接收訊息丟擲異常
            mw.showToast("接受資訊異常");
            mw.heart(so);
        }
    }
}

讀取訊息的程式碼比較簡單的,除了心跳機制,其他的都是在Java套接字中的常規操作,不多說了。

主視窗邏輯(整合功能及附加功能)

package com.example.a9042.chatclint;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;


import com.example.a9042.socket.MyScoket;
import com.example.a9042.socket.Receive;

import java.io.IOException;
import java.net.Socket;


public class MainWindow extends AppCompatActivity implements View.OnClickListener {

    private Handler myHnadler;      //myHnadler用於在子執行緒中對UI的修改
    public String name;             //使用者名稱
    private AlertDialog dialog;     //顯示彈窗

    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_window);

        showInputName();            //顯示彈窗

        myHnadler=new Handler(){
          @Override
          public void handleMessage(Message mes){
              TextView hTest = findViewById(R.id.HistoryTest);
              TextView OnLine =findViewById(R.id.online);
              if(mes.what==0x0002)
                  hTest.append(mes.obj.toString());
              if(mes.what==0x0001)
                  OnLine.setText("當前線上人數:"+mes.obj.toString());
          }
        };

        Button Send = findViewById(R.id.Send);//獲得傳送按鈕例項
        TextView hText=findViewById(R.id.HistoryTest);//獲得顯示區域例項
        hText.setGravity(Gravity.BOTTOM);//將TextView設定為從底部填寫

        Send.setOnClickListener(this);//為按鈕繫結監聽事件
    }

    MyScoket ms=new MyScoket(this);//例項化MySocket物件

    /*按鈕繫結監聽事件邏輯*/
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.Send:{
                EditText sTest = findViewById(R.id.Message);//獲取編輯框例項
                if(sTest.getText().toString().length()>0) {
                    ms.send(sTest.getText().toString());//如果編輯框有內容,就將這個內容傳送到伺服器上
                    sTest.setText("");//編輯框置空
                }
            }break;
        }
    }
    /*顯示特定內容的彈出訊息*/
    public void showToast(final String str){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainWindow.this,str,Toast.LENGTH_SHORT).show();
            }
        });
    }
    /*根據引數的標誌對UI進行相應的修改*/
    public void getMessage(final String str){
        switch (str.substring(0, 3)) {
            case "mes"://如果引數標誌為mes,則在textview追加填寫str
                myHnadler.sendMessage(Message.obtain(myHnadler,0x0002,str.substring(3)));
                break;
            case "per"://如果引數標誌為pre,則更新線上人數
                myHnadler.sendMessage(Message.obtain(myHnadler,0x0001,str.substring(3)));
        }
    }

    /*心跳方法,用來保證套接字長連線,其實就是向伺服器傳送一個檢驗位*/
    public void heart(Socket s){
        if(s!=null){
            try {
                s.sendUrgentData(0xff);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /*彈窗顯示方法*/
    public void showInputName(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);  //先得到構造器
        View view1=View.inflate(this,R.layout.input_name,null);
        Button getName=view1.findViewById(R.id.get_name);//獲得彈窗按鈕例項
        final EditText inputName=view1.findViewById(R.id.input_name);//獲得彈窗編輯框例項
        builder.setTitle("你的名字")//設定彈窗標題
                .setView(view1)
                .create();
        final AlertDialog show=builder.show();//顯示彈窗

        /*為彈窗按鈕新增點選事件*/
        getName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                name=inputName.getText().toString();//將name賦值為編輯框內容
                ms.start();//開始嘗試連線伺服器
                show.dismiss();//關閉彈窗
            }
        });
    }
}

看著註釋,難度應該不算大,這裡有兩個點需要注意一下,一個是上面說的心跳機制,就在97行的程式碼處;另一個是非常重要的一點:在子執行緒更新UI的問題。

子執行緒更新UI(Handler 的使用)

大家知道執行緒分為主執行緒(一個)和子執行緒(多個),假如在子執行緒裡面更新UI會有什麼後果呢?是的,這就會發生所謂的執行緒阻塞。在這裡,我們就需要使用Handler 來進行在子執行緒中更新UI了。
Handler 是什麼呢?Handler 其實就是一套訊息處理機制,我們可以用它來發訊息,也可以用它來處理訊息。
這時候可能會問,訊息處理機制和更新UI有什麼關係呢?其實不然。我們可以在主執行緒裡面例項化這個Handler ,使其只在頁面主執行緒中執行,然後如果有哪個子執行緒需要去修改UI,那麼就向主執行緒的Handler 發訊息,Handler 收到訊息後,對訊息進行處理,並作出相應的反應,從而達到更新UI的效果。
Handler 的作用大致意思上是這些,如果想要深入的瞭解Handler 在Android中的應用,敬請自己百度。

3、專案地址