安卓android+WebSocket實現簡易QQ聊天室

本篇僅介紹實現聊天室的前端,也就是安卓端代碼,後端的實現看鏈接說明

鏈接說明

1.後端使用了spring boot 框架,若不熟悉,有關spring boot 入門教程請戳此鏈接使用Intellij IDEA開發第一個spring boot項目
2.websocket後端實現細節戳此鏈接spring boot練習–利用websocket實現QQ聊天室

界面展示

說明

有兩個界面,第一個是登陸界面,我借用了我之前實現的登陸界面,並做了一些微調。需要輸入ID和名字,測試的時候輸入的ID不能重複,第二個是名字用於界面展示,登陸後跳轉入第二個界面,就可以在聊天室裏聊天了。

下面圖模擬了一組場景,大青兒先進入聊天室,然後小明進入,互相發一段消息後,小明退出聊天室。

大青兒界面變化

在這裏插入圖片描述

小明界面變化

在這裏插入圖片描述

代碼部分

這裏僅展示安卓的代碼,後端代碼詳見上面鏈接說明。

文件的簡單介紹

Activity文件:LoginActivity.java,ChatActivity.java,與其對應佈局文件activity_login.xml,activity_chat.xml
模型文件:Msg.java,User.java
適配器:UserAdapter
子佈局文件:user_item.xml

第一步修改.gradle文件,添加以下內容
    implementation 'com.squareup.okhttp3:okhttp:3.8.1'
    implementation 'com.squareup.okhttp3:mockwebserver:3.8.1'

    implementation 'com.alibaba:fastjson:1.2.10'
    implementation 'com.android.support:recyclerview-v7:28.0.0'
第二步,修改AndroidManifest.xml文件,添加以下內容
 <activity android:name=".ChatActivity">
 </activity>
 <activity android:name=".LoginActivity">
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />

         <category android:name="android.intent.category.LAUNCHER"/>
     </intent-filter>
 </activity>
以及添加這行,注意添加的位置的區別
<uses-permission android:name="android.permission.INTERNET" />
第三步具體功能代碼
LoginActivity.java
public class LoginActivity extends Activity {
    private EditText mEditTextName,mEditTextId;
    private Button mButtonLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        init();

        mButtonLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userName = mEditTextName.getText().toString();
                String userId = mEditTextId.getText().toString();
                User user = new User(userId,userName);
                Intent intent = ChatActivity.newIntent(LoginActivity.this,user.toString());
                startActivity(intent);
                LoginActivity.this.finish();
            }
        });
    }

    private void init(){
        mEditTextName = findViewById(R.id.nickname_editText);
        mEditTextId = findViewById(R.id.id_editText);
        mEditTextName.setCompoundDrawables(initDrawable(R.drawable.nickname),null,null,null);
        mEditTextId.setCompoundDrawables(initDrawable(R.drawable.id),null,null,null);
        mButtonLogin = findViewById(R.id.login);
    }

    /**
     * 設置EditText左邊圖片的大小
     * */
    private Drawable initDrawable(int res){
        Drawable drawable = getResources().getDrawable(res);
        //距離左邊距離,距離上邊距離,長,寬
        drawable.setBounds(0,0,100,100);
        return drawable;
    }
}
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background">

    <!--    https://www.runoob.com/w3cnote/android-tutorial-relativelayout.html-->

    <RelativeLayout
        android:id="@+id/viewTop"
        android:layout_width="match_parent"
        android:layout_height="240dp"
        android:background="@color/mihuang">

        <ImageView
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="10dp"
            android:src="@drawable/boy" />
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/viewCenter"
        android:paddingTop="20dp"
        android:layout_centerHorizontal="true"
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/viewTop"
        android:paddingBottom="70dp">

        <EditText
            android:id="@+id/id_editText"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:drawablePadding="10dp"
            android:drawableLeft="@drawable/id"
            android:hint="請輸入Id"
            android:textSize="16sp"
            android:layout_marginLeft="10dp"/>

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_alignParentRight="true"
            android:src="@drawable/down"
            android:paddingRight="10dp"
            android:paddingTop="20dp"/>

        <EditText
            android:layout_below="@+id/id_editText"
            android:id="@+id/nickname_editText"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:drawablePadding="10dp"
            android:drawableLeft="@drawable/nickname"
            android:hint="請輸入暱稱"
            android:layout_marginLeft="10dp"
            android:textSize="16sp"/>

    </RelativeLayout>

    <Button
        android:layout_below="@+id/viewCenter"
        android:layout_centerHorizontal="true"
        android:id="@+id/login"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:text="登陸"
        android:textColor="@color/text"
        android:background="@color/login"
        android:textSize="25dp" />

    <RelativeLayout
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="10dp">
        <TextView
            android:text="無法登陸?"
            android:paddingLeft="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:text="新用戶註冊"
            android:paddingRight="10dp"
            android:layout_alignParentRight="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RelativeLayout>

</RelativeLayout>
ChatActivity.java
public class ChatActivity extends Activity {
    private ArrayList<User> mUserArrayList = new ArrayList<>();//定義一個存儲信息的列表
    private EditText mInputText;//輸入框
    private Button mSend;//發送按鈕
    private RecyclerView mRecyclerView;//滑動框
    private UserAdapter mAdapter;//適配器
    private boolean backFlag = false;
    private WebSocket mSocket;


    private User mUser;;//全局User

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

        String data = getIntent().getStringExtra("data");
        if (!data.equals("")){
            mUser = JSON.parseObject(data,User.class);
        }else {
            mUser = new User("0001","測試名字",R.drawable.boy);
        }


        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(layoutManager);
        mAdapter = new UserAdapter(mUserArrayList);
        mRecyclerView.setAdapter(mAdapter);

        //開啓連接
        start(mUser.getUserId());

        mSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = mInputText.getText().toString();
                if (!"".equals(content)) {
                    Msg msg = new Msg(true,content,false);
                    User tempUser = new User(mUser.getUserId(),mUser.getUserName(),R.drawable.boy,msg);
                    mSocket.send(tempUser.toString());
                    mUserArrayList.add(tempUser);
                    updateRecyclerView();//刷新RecyclerView
                    //清空輸入欄
                    mInputText.setText("");
                }
            }
        });

    }

    /**
     * 刷新view
     * */
    private void updateRecyclerView(){
        //當有新消息時,刷新RecyclerView中的顯示
        mAdapter.notifyItemInserted(mUserArrayList.size() - 1);
        //將RecyclerView定位到最後一行
        mRecyclerView.scrollToPosition(mUserArrayList.size() - 1);
    }

    /**
     * 開啓web socket連接
     * */
    private void start(String userId) {

        OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
                .readTimeout(300, TimeUnit.SECONDS)//設置讀取超時時間
                .writeTimeout(300, TimeUnit.SECONDS)//設置寫的超時時間
                .connectTimeout(300, TimeUnit.SECONDS)//設置連接超時時間
                .build();

        //定義request
        Request request = new Request.Builder().url("ws://192.168.5.10:8080/test/"+userId).build();
        //綁定回調接口
        mOkHttpClient.newWebSocket(request, new EchoWebSocketListener());
        mOkHttpClient.dispatcher().executorService().shutdown();

    }

    /**
     * 顯示內容
     * */
    private void output(final User user) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mUserArrayList.add(user);
                updateRecyclerView();
            }
        });
    }

    /**
     * 初始化界面
     * */
    private void initView(){
        mInputText = findViewById(R.id.input_text);
        mSend = findViewById(R.id.send);
        mRecyclerView = findViewById(R.id.msg_recycler_view);
    }

    /**
     * 靜態方法返回一個能啓動自己的intent
     * */
    public static Intent newIntent(Context context,String data){
        Intent intent = new Intent(context,ChatActivity.class);
        intent.putExtra("data",data);
        return intent;
    }

    /**
     * 對返回鍵的處理
     * */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event){
        if(keyCode==KeyEvent.KEYCODE_BACK&&!backFlag){
            Toast.makeText(ChatActivity.this,"再按一次退出程序",Toast.LENGTH_SHORT).show();
            backFlag = true;
            return true;
        }else {
            return super.onKeyDown(keyCode, event);
        }
    }

    /**
     * 內部類,監聽web socket回調
     * */
    private final class EchoWebSocketListener extends WebSocketListener {

        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            super.onOpen(webSocket, response);
            mSocket = webSocket;    //實例化web socket
            User user = new User();
            user.setUserMsg(new Msg(false,"連接成功",true));
            output(user);
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);
            User user = JSON.parseObject(text, User.class);
            output(user);
        }

        @Override
        public void onClosed(WebSocket webSocket, int code, String reason) {
            super.onClosed(webSocket, code, reason);
            User user = new User();
            user.setUserMsg(new Msg(false,"關閉連接",true));
            output(user);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            super.onFailure(webSocket, t, response);
            User user = new User();
            user.setUserMsg(new Msg(false,"連接失敗:"+t.getMessage(),true));
            output(user);
        }
    }

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

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/msg_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:maxLines="2"
            android:hint="請輸入……"
            />
        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發送"/>
    </LinearLayout>
</LinearLayout>
Msg.java
public class Msg {
    private boolean send;//是否是發送的消息
    private String content;//發送的內容
    private boolean system;//是否是系統消息

    public Msg(boolean send, String content) {
        this.send = send;
        this.content = content;
        this.system = false;
    }

    public Msg() {
    }

    public Msg(boolean send, String content, boolean system) {
        this.send = send;
        this.content = content;
        this.system = system;
    }

    public boolean isSystem() {
        return system;
    }

    public void setSystem(boolean system) {
        this.system = system;
    }



    public boolean isSend() {
        return send;
    }

    public void setSend(boolean send) {
        this.send = send;
    }

    public String getContent() {
        return content;
    }

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

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}
User.java
public class User {
    private String userId;
    private String userName;
    private int userImg;
    private Msg userMsg;

    public int getUserImg() {
        return userImg;
    }

    public void setUserImg(int userImg) {
        this.userImg = userImg;
    }

    public User(String userId, String userName, int userImg) {
        this.userId = userId;
        this.userName = userName;
        this.userImg = userImg;
    }

    public User() {
    }

    public User(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

    public User(String userId, String userName, Msg userMsg) {
        this.userId = userId;
        this.userName = userName;
        this.userMsg = userMsg;
    }

    public User(String userId, String userName, int userImg, Msg userMsg) {
        this.userId = userId;
        this.userName = userName;
        this.userImg = userImg;
        this.userMsg = userMsg;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Msg getUserMsg() {
        return userMsg;
    }

    public void setUserMsg(Msg userMsg) {
        this.userMsg = userMsg;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}
UserAdapter.java
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder>{

    private ArrayList<User> mUsers;

    public UserAdapter(ArrayList<User> users) {
        mUsers = users;
    }

    @NonNull
    @Override
    public UserAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.user_item,parent,false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) {
        User user = mUsers.get(position);
        if (user.getUserMsg().isSystem()){//系統消息
            holder.mLinearLayoutSystem.setVisibility(View.VISIBLE);
            holder.mRelativeLayoutReceive.setVisibility(View.GONE);
            holder.mRelativeLayoutSend.setVisibility(View.GONE);
            holder.mTextViewSystemText.setText(user.getUserMsg().getContent());
        }else{
            if (user.getUserMsg().isSend()){//是發送消息
                holder.mLinearLayoutSystem.setVisibility(View.GONE);
                holder.mRelativeLayoutReceive.setVisibility(View.GONE);
                holder.mRelativeLayoutSend.setVisibility(View.VISIBLE);

                holder.mImageViewSend.setImageResource(user.getUserImg());
                holder.mTextViewNameSend.setText(user.getUserName());
                holder.mTextViewContentSend.setText(user.getUserMsg().getContent());
            }else { //接收的消息
                holder.mLinearLayoutSystem.setVisibility(View.GONE);
                holder.mRelativeLayoutReceive.setVisibility(View.VISIBLE);
                holder.mRelativeLayoutSend.setVisibility(View.GONE);

                holder.mImageViewReceive.setImageResource(user.getUserImg());
                holder.mTextViewNameReceive.setText(user.getUserName());
                holder.mTextViewContentReceive.setText(user.getUserMsg().getContent());
            }
        }

    }

    @Override
    public int getItemCount() {
        return mUsers.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        //綁定所有視圖
        RelativeLayout mRelativeLayoutSend,mRelativeLayoutReceive;
        LinearLayout mLinearLayoutSystem;
        ImageView mImageViewReceive,mImageViewSend;
        TextView mTextViewNameReceive,mTextViewNameSend,mTextViewContentReceive,mTextViewContentSend,mTextViewSystemText;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mRelativeLayoutReceive = itemView.findViewById(R.id.receive_relative);
            mRelativeLayoutSend = itemView.findViewById(R.id.send_relative);
            mLinearLayoutSystem = itemView.findViewById(R.id.system_layout);

            mImageViewReceive = itemView.findViewById(R.id.image_receive);
            mImageViewSend = itemView.findViewById(R.id.image_send);

            mTextViewNameReceive = itemView.findViewById(R.id.text_name_receive);
            mTextViewNameSend = itemView.findViewById(R.id.text_name_send);
            mTextViewContentReceive = itemView.findViewById(R.id.text_content_receive);
            mTextViewContentSend = itemView.findViewById(R.id.text_content_send);
            mTextViewSystemText = itemView.findViewById(R.id.system_text);
        }
    }
}
user_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:layout_marginBottom="10dp">
    <RelativeLayout
        android:id="@+id/receive_relative"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_alignParentLeft="true"
            android:id="@+id/image_receive"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginTop="20dp"
            android:layout_marginLeft="10dp"
            android:src="@drawable/boy"/>
        <LinearLayout
            android:layout_toRightOf="@id/image_receive"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/text_name_receive"
                android:layout_width="50dp"
                android:layout_height="20dp"
                android:layout_gravity="left"
                android:text="大青兒"
                android:textSize="15dp"
                android:textColor="#131313"
                android:layout_marginLeft="20dp"/>
            <LinearLayout
                android:id="@+id/layout_content_receive"
                android:layout_gravity="right"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/message_left">
                <TextView
                    android:id="@+id/text_content_receive"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_margin="5dp"
                    android:textSize="20dp"
                    android:textColor="#F5EFEF"
                    android:text="我是一段文字一段文"
                    />
            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/send_relative"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:layout_alignParentRight="true"
            android:id="@+id/image_send"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginTop="20dp"
            android:layout_marginRight="10dp"
            android:src="@drawable/boy"/>
        <LinearLayout
            android:layout_toLeftOf="@id/image_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <TextView
                android:id="@+id/text_name_send"
                android:layout_width="50dp"
                android:layout_height="20dp"
                android:layout_gravity="right"
                android:text="大青兒"
                android:textSize="15dp"
                android:textColor="#131313"
                android:layout_marginRight="20dp"/>

            <LinearLayout
                android:id="@+id/layout_content_send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:background="@drawable/message_right">

                <TextView
                    android:id="@+id/text_content_send"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_margin="5dp"
                    android:text="我是一段文字一段文"
                    android:textSize="20dp" />
            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>

    <LinearLayout
        android:id="@+id/system_layout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/system_text"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:text="系統消息:"
            android:layout_gravity="center"
            android:textColor="#F17070"/>
    </LinearLayout>
</RelativeLayout>

圖片資源

應用中大部分圖片資源都是隻起裝飾性作用,可以在阿里巴巴矢量圖標庫獲取,當然包裹聊天文字的圖片是.9格式圖片,可以參考:Android Studio製作.9圖片

提醒一哈

先配置好後端再進行前端測試,不然沒連上服務器會報錯並閃退。因爲當你點發送的時候,有一段代碼中websocket並沒有被實例化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章