android環境下的即時通訊

来源:http://www.cnblogs.com/huangjie123/archive/2016/12/07/6142311.html
-Advertisement-
Play Games

Socket,又稱“套接字”, 在應用層和傳輸層之間的一個抽象層,用於描述 IP 地址和埠,是一個通信連的句柄, ...


       首先瞭解一下即時通信的概念。通過消息通道 傳輸消息對象,一個賬號發往另外一賬號,只要賬號線上,可以即時獲取到消息,這就是最簡單的即使通訊。消息通道可由TCP/IP UDP實現。通俗講就是把一個人要發送給另外一個人的消息對象(文字,音視頻,文件)通過消息通道(C/S實時通信)進行傳輸的服務。即時通訊應該包括四種形式,線上直傳、線上代理、離線代理、離線擴展。線上直傳指不經過伺服器,直接實現點對點傳輸。線上代理指消息經過伺服器,在伺服器實現中轉,最後到達目標賬號。離線代理指消息經過伺服器中轉到達目標賬號,對方不線上時消息暫存伺服器的資料庫,在其上線再傳發。離線擴展指將暫存消息以其它形式,例如郵件、簡訊等轉發給目標賬號。

       此外,我們還需要認識一下電腦網路相關的概念。經典的電腦網路四層模型中,TCP和UDP是傳輸層協議,包含著消息通信內容。ip為網路層協議,是一種網路地址。TCP/IP,即傳輸控制協議/網間協議,定義了主機如何連入網際網路及數據如何在它們之間傳輸的標準。Socket,又稱“套接字”, 在應用層和傳輸層之間的一個抽象層,用於描述 IP 地址和埠,是一個通信連的句柄,應用程式通常通過“套接字”向網路發送請求或者應答網路請求,它就是網路通信過程中端點的抽象表示。它把TCP/IP層複雜的操作抽象為幾個簡單的介面供應用層調用已實現進程在網路中通信。XMPP(可擴展消息處理現場協議)是基於可擴展標記語言(XML)的協議,應用於即時通訊場景的應用層協議,底層通過Socket實現。它用於即時消息(IM)以及線上現場探測。它在促進伺服器之間的準即時操作。這個協議可能最終允許網際網路用戶向網際網路上的其他任何人發送即時消息, 即使其操作系統和瀏覽器不同。這樣實現即時通訊就有兩種方案,一是從套接字入手,直接利用socket提供的介面進行數據的傳送。二是藉助開源工具(伺服器openfire),用XMPPConnection創建連接。

     XMPP是實現即時通訊使用較為普遍的做法。XMPP中,各項工作都是通過在一個 XMPP 流上發送和接收 XMPP 節來完成的。核心 XMPP 工具集由三種基本節組成,這三種節分別為<presence>、出席<message>、<iq>。XMPP 流由兩份 XML 文檔組成,通信的每個方向均有一份文檔。這份文檔有一個根元素<stream:stream>,這個根元素的子元素由可路由的節以及與流相關的頂級子元素構成。xmpp協議同樣包括客戶端和伺服器。客戶端基於 Android 平臺進行開發。負責初始化通信過程,進行即時通信時,由客戶端負責向伺服器發起創建連接請求。系統通過 GPRS 無線網路與Internet 網路建立連接,通過伺服器實現與 Android 客戶端的即時通信腳。伺服器端則採用 Openfire 作為伺服器。 允許多個客戶端同時登錄並且併發的連接到一個伺服器上。伺服器對每個客戶端的連接進行認證,對認證通過的客戶端創建會話,客戶端與伺服器端之間的通信就在該會話的上下文中進行。使用了 asmark 開源框架實現的即時通訊功能.該框架基於開源的 XMPP 即時通信協議,採用 C/S 體繫結構,通過 GPRS 無線網路用TCP 協議連接到伺服器,以架設開源的 Openfn'e 伺服器作為即時通訊平臺。xmpp消息通道的創建:

          先配置通道信息進行連接

            ConnectionConfiguration configuration = new ConnectionConfiguration(HOST, PORT),

          設置Debug信息和安全模式

            configuration.setDebuggerEnabled(true);

            configuration.setSecurityMode(SecurityMode.disabled),

          最後才是建立連接

           conn.connect();

         在ContentObserver的實現類中觀察消息變化。XMPPConnection.getRoster()獲取聯繫人列表對象。用xmpp協議編寫通訊協議的大致思路可以如下。進入登陸界面,通過xmppconnection的login方法實現登陸,登陸成功進入主界面。主界麵包含兩個Fragment,分別用來顯示聯繫人和聊天記錄。創建聯繫人和簡訊的數據觀察者,在聯繫人、簡訊服務中分別設定監聽RosterListener()、ChatManagerListener(),接受聯繫人和簡訊信息,同時將相關信息添加到內容提供者中。在內容提供者中設定一個內容觀察者,當數據發生變化時通知界面更新。

        本文的重點是利用Socket的介面實現即時通訊,因為絕大多數即時通訊的底層都是通過Socket實現的。其基本的業務邏輯可描述如下。用戶進入登陸界面後,提交賬號密碼 經服務端確定,返回相關參數用於確定連接成功。進入聊天界面或好友界面。點擊聯繫人或聊天記錄的條目,進入聊天界面。當移動端再次向伺服器發送消息時,由伺服器轉發消息內容給目標賬號。同時更新界面顯示。這樣就完成即時通訊的基本功能。當然,也可以添加一個後臺服務,當用戶推出程式時,在後臺接受消息。不難看出,對於即時通訊來講,有三個關註點:消息通道、消息內容、消息對象。因此,主要邏輯也是圍繞這三個點展開。消息通道實現傳輸消息對象的發送和接收。為Socket(String host, int port)傳入服務其地址和埠號,即可創建連接。消息內容的格式應該與伺服器保持一致。接受數據時,獲取輸入流並用DataInputStream包裝,通過輸入流讀取server發來的數據。發送數據時,獲取輸出流並用DataOutputStream包裝,通過輸出流往server發送數據。消息內容中應該包括發送者、接受者信息、數據類型等。消息對象就是消息的發送者和消息的接受者。接下來在代碼中進行詳細的講解。

         創建一個消息的基類,實現xml文件和字元串的轉換,用到Xsream第三方jar包。這樣當創建消息類時,繼承該方法,就可以直接在類中實現數據的轉換。

/**
 * Created by huang on 2016/12/3.
 */
public class ProtacolObjc implements Serializable {
    public String toXml() {
        XStream stream = new XStream();
        //將根節點轉換為類名
        stream.alias(this.getClass().getSimpleName(), this.getClass());
        return stream.toXML(this);
    }

    public Object fromXml(String xml) {
        XStream x = new XStream();
        x.alias(this.getClass().getSimpleName(), this.getClass());
        return x.fromXML(xml);
    }
    
    //創建Gson數據和字元串之間轉換的方法,適應多種數據
    public String toGson() {
        Gson gson = new Gson();
        return toGson();
    }

    public Object fromGson(String result) {
        Gson gson = new Gson();
        return gson.fromJson(result, this.getClass());
    }
}

 

         創建線程工具,指定方法運行在子線程和主線程中。由於網路操作需要在子線程中,界面更新需要在主線程中,創建線程工具可以方便選擇線程。

import android.os.Handler;
/**
 * Created by huang on 2016/12/5.
 */
public class ThreadUtils {
    private static Handler handler = new Handler();
    public static void runUIThread(Runnable r){
        handler.post(r);
    }
    public static void runINThread(Runnable r){
        new Thread(r).start();
    }
}

          創建消息的工具類,包括消息內容、消息類型、消息本省等。由於伺服器返回的內容中包含消息的包名信息所以消息本身的包名應該於服務其保持一直。

/**
 * Created by huang on 2016/12/3.
 * 消息內容
 */
public class QQMessage extends ProtacolObjc {
    public String type = QQmessageType.MSG_TYPE_CHAT_P2P;// 類型的數據 chat login
    public long from = 0;// 發送者 account
    public String fromNick = "";// 昵稱
    public int fromAvatar = 1;// 頭像
    public long to = 0; // 接收者 account
    public String content = ""; // 消息的內容 約不?
    public String sendTime = getTime(); // 發送時間

    public String getTime() {
        Date date = new Date(System.currentTimeMillis());
        java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("mm-DD HH:mm:ss");
        return format.format(date);
    }

    public String getTime(Long time) {
        Date date = new Date(time);
        java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("mm-DD HH:mm:ss");
        return format.format(date);
    }
}

/**
 * Created by huang on 2016/12/3.
 * 消息類型
 */
public class QQmessageType {
    public static final String MSG_TYPE_REGISTER = "register";// 註冊
    public static final String MSG_TYPE_LOGIN = "login";// 登錄
    public static final String MSG_TYPE_LOGIN_OUT = "loginout";// 登出
    public static final String MSG_TYPE_CHAT_P2P = "chatp2p";// 聊天
    public static final String MSG_TYPE_CHAT_ROOM = "chatroom";// 群聊
    public static final String MSG_TYPE_OFFLINE = "offline";// 下線
    public static final String MSG_TYPE_SUCCESS = "success";//成功
    public static final String MSG_TYPE_BUDDY_LIST = "buddylist";// 好友
    public static final String MSG_TYPE_FAILURE = "failure";// 失敗
}

import com.example.huang.imsocket.bean.ProtacolObjc;
/*
 *消息本身 包括 賬號、頭像和昵稱
 *
 */
public class QQBuddy extends ProtacolObjc {
    public long account;
    public String nick;
    public int avatar;
}

/**
 * Created by huang on 2016/12/3.
 */

public class QQBuddyList extends ProtacolObjc {
    public ArrayList<QQBuddy> buddyList = new ArrayList<>();
}

           關於socket的創建連接和發送消息、接受消息。

import android.util.Log;
import com.example.huang.imsocket.bean.QQMessage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by huang on 2016/12/3.
 * 連接 伺服器
 */
public class QQConnection extends Thread {
    private static final String TAG = "QQConnection";
    private Socket client;
    private DataOutputStream write;
    private DataInputStream read;
    public static final String HOST = "192.168.23.48";
    public static final int POST = 5225;

    private boolean flag = true;

    private List<OnQQmwssagereceiveLisener> mOnQQmwssagereceiveLisener = new ArrayList<>();

    public void addOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisener lisener) {
        mOnQQmwssagereceiveLisener.add(lisener);
    }

    public void removeOnQQmwssagereceiveLisener(OnQQmwssagereceiveLisener lisener) {
        mOnQQmwssagereceiveLisener.remove(lisener);
    }

    public interface OnQQmwssagereceiveLisener {
        public void onReiceive(QQMessage qq);
    }

    @Override
    public void run() {
        super.run();
        while (flag) {
            try {
                String utf = read.readUTF();
                QQMessage message = new QQMessage();
                QQMessage msg = (QQMessage) message.fromXml(utf);
                if (msg != null) {
                    for (OnQQmwssagereceiveLisener lisner : mOnQQmwssagereceiveLisener)
                        lisner.onReiceive(msg);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void connect() {
            try {
                if (client == null) {
                    client = new Socket(HOST, POST);
                    write = new DataOutputStream(client.getOutputStream());
                    read = new DataInputStream(client.getInputStream());
                    flag = true;
                    this.start();
                    Log.e(TAG, "connect: "+(write==null)+"---"+ (read == null));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    public void disconnect() {
        if (client != null) {
            flag = false;
            this.stop();
            try {
                read.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                write.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void send(String xml) throws IOException {
        write.writeUTF(xml);
        write.flush();
    }

    public void send(QQMessage qq) throws IOException {
        write.writeUTF(qq.toXml());
        write.flush();
    }
}

         閃屏界面的佈局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_splash"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/splash_bg">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@mipmap/conversation_bg_logo" />
</RelativeLayout>

 

       
          閃屏界面,保持4秒鐘進入登陸界面。一般來見,閃屏界面可以載入數據、獲取版本號、更新版本等操作。這裡沒有做的那麼複雜。

import com.example.huang.imsocket.R;

public class SplashActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();   //隱藏標欄
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);  //全屏顯示
        setContentView(R.layout.activity_splash);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(SplashActivity.this, LoginActivity.class));
                finish();
            }
        }, 4000);
    }
}

        登陸界面的佈局

<?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:background="#aabbdd"
    android:gravity="center"
    android:orientation="vertical">

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/conversation_bg_logo" />

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="8dp"
            android:gravity="center_horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:text="賬號:"
                android:textColor="#000" />

            <EditText
                android:id="@+id/et_accoun"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:gravity="center"
                android:hint="輸入賬號" />
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="4dp"
            android:gravity="center_horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:text="密碼:"
                android:textColor="#000" />

            <EditText
                android:id="@+id/et_pwd"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="3"
                android:gravity="center"
                android:hint="輸入密碼" />
        </TableRow>

        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="80dp"
            android:layout_marginRight="80dp"
            android:layout_marginTop="8dp"
            android:onClick="sendmessage"
            android:text="登錄" />

    </TableLayout>
</LinearLayout>

     登陸界面,創建和伺服器的連接,向伺服器發送登陸信息,接受伺服器返回的信息。

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

import com.example.huang.imsocket.R;
import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQBuddyList;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.bean.QQmessageType;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.service.IMService;
import com.example.huang.imsocket.util.ThreadUtils;

import java.io.IOException;

/**
 * Created by huang on 2016/12/3.
 */
public class LoginActivity extends Activity {
    private static final String TAG = "LoginActivity";

    private EditText et_accoun;
    private EditText et_pwd;

    private String accoun;
    private QQConnection conn;
    private QQConnection.OnQQmwssagereceiveLisener lisener = new QQConnection.OnQQmwssagereceiveLisener() {
        @Override
        public void onReiceive(final QQMessage qq) {

            final QQBuddyList list = new QQBuddyList();
            final QQBuddyList list2 = (QQBuddyList) list.fromXml(qq.content);
            if (QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)) {
                ThreadUtils.runUIThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getBaseContext(), "成功", Toast.LENGTH_SHORT).show();

                        Myapp.me = conn;
                        Myapp.username = accoun;
                        Myapp.account = accoun + "@qq.com";

                        Intent intent = new Intent(LoginActivity.this, contactActivity.class);
                        intent.putExtra("list", list2);
                        startActivity(intent);

                        Intent data = new Intent(LoginActivity.this, IMService.class);
                        startService(data);
                        finish();
                    }
                });
            } else {
                ThreadUtils.runUIThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getBaseContext(), "登陸失敗", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        et_accoun = (EditText) findViewById(R.id.et_accoun);
        et_pwd = (EditText) findViewById(R.id.et_pwd);
        ThreadUtils.runINThread(new Runnable() {
            @Override
            public void run() {
                try {
                    conn = new QQConnection();
                    conn.addOnQQmwssagereceiveLisener(lisener);
                    conn.connect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void sendmessage(View view) {
        accoun = et_accoun.getText().toString().trim();
        final String password = et_pwd.getText().toString().trim();
        Log.i(TAG, "sendmessage: " + accoun + "#" + password);
        ThreadUtils.runINThread(new Runnable() {
            @Override
            public void run() {
                QQMessage message = new QQMessage();
                message.type = QQmessageType.MSG_TYPE_LOGIN;
                message.content = accoun + "#" + password;
                String xml = message.toXml();
                if (conn != null) {
                    try {
                        conn.send(xml);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        conn.removeOnQQmwssagereceiveLisener(lisener);
    }
}

 

         好友列表界面

<?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:background="#aabbcc"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="聯繫人列表"
        android:textColor="#6d00"
        android:textSize="23dp" />

    <ListView
        android:id="@+id/lv_contact"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</LinearLayout>

 

         好友列表及時收到從哪個服務其發揮的好友更新信息,點擊條目跳到聊天界面。

import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.huang.imsocket.R;
import com.example.huang.imsocket.bean.Myapp;
import com.example.huang.imsocket.bean.QQBuddyList;
import com.example.huang.imsocket.bean.QQMessage;
import com.example.huang.imsocket.bean.QQmessageType;
import com.example.huang.imsocket.core.QQConnection;
import com.example.huang.imsocket.util.ThreadUtils;

import java.util.ArrayList;

import butterknife.Bind;
import butterknife.ButterKnife;
import cn.itcast.server.bean.QQBuddy;

/**
 * Created by huang on 2016/12/5.
 */

public class contactActivity extends Activity {
    private static final String TAG = "contactActivity";
    @Bind(R.id.tv_title)
    TextView tv_title;
    @Bind(R.id.lv_contact)
    ListView lv_contact;
    private QQBuddyList list;
    private ArrayList<QQBuddy> BuddyList = new ArrayList<>();
    private ArrayAdapter adapter = null;

    private QQConnection.OnQQmwssagereceiveLisener listener = new QQConnection.OnQQmwssagereceiveLisener() {
        @Override
        public void onReiceive(QQMessage qq) {
            if (QQmessageType.MSG_TYPE_BUDDY_LIST.equals(qq.type)) {
                QQBuddyList qqlist = new QQBuddyList();
                QQBuddyList qqm = (QQBuddyList) qqlist.fromXml(qq.content);
                BuddyList.clear();
                BuddyList.addAll(qqm.buddyList);
                ThreadUtils.runUIThread(new Runnable() {
                    @Override
                    public void run() {
                        saveAndNotify();
                    }
                });
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);
        ButterKnife.bind(this);
        Myapp.me.addOnQQmwssagereceiveLisener(listener);
        Intent intent = getIntent();
        list = (QQBuddyList) intent.getSerializableExtra("list");
        BuddyList.clear();
        BuddyList.addAll(list.buddyList);
        saveAndNotify();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Myapp.me.removeOnQQmwssagereceiveLisener(listener);
    }

    private void saveAndNotify() {
        if (BuddyList.size() < 1) {
            return;
        }
        if (adapter == null) {
            adapter = new ArrayAdapter<QQBuddy>(getBaseContext(), 0, BuddyList) {
                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                    viewHolder holder;
                    if (convertView == null) {
                        convertView = View.inflate(getContext(), R.layout.item_contacts, null);
                        holder = new viewHolder(convertView);
                        convertView.setTag(holder);
                    } else {
                        holder = (viewHolder) convertView.getTag();
                    }
                    QQBuddy qqBuddy = BuddyList.get(position);
                    holder.tv_nick.setText(qqBuddy.nick);
                    holder.tv_account.setText(qqBuddy.account + "@qq.com");

                    if (Myapp.username.equals(qqBuddy.account + "")) {
                        holder.tv_nick.setText("[自己]");
                        holder.tv_nick.setTextColor(Color.GRAY);
                    } else {
                        holder.tv_nick.setTextColor(Color.RED);
                    }
                    return convertView;
                }
            };
            lv_contact.setAdapter(adapter);

            lv_contact.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    QQBuddy qqbuddy = BuddyList.get(position);
                    if (Myapp.username.equals(qqbuddy.account + "")) {
                        Toast.makeText(getBaseContext(), "不能和自己聊天", Toast.LENGTH_SHORT).show();
                    } else {
                        Intent intent = new Intent(contactActivity.this, ChatActivity.class);
                        intent.putExtra("account", qqbuddy.account + "");
                        intent.putExtra("nick", qqbuddy.nick + "");
                        startActivity(intent);
                    }
                }
            });
        } else {
            adapter.notifyDataSetChanged();
        }
    }

    static class viewHolder {
        @Bind(R.id.iv_contact)
        ImageView iv_contact;
        @Bind(R.id.tv_nick)
        TextView tv_nick;
        @Bind(R.id.tv_account)
        TextView tv_account;
        public viewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }
}

 

        聊天界面

<?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">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#aa119988"
        android:gravity="center"
        android:text="和誰誰聊天中........."
        android:textSize="19dp" />

    <ListView
        android:id="@+id/lv_chat"
        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="horizontal">

        <EditText
            android:id="@+id/et_sms"
            android:layout_width="0dp"
            android:layout_height="40dp"
            android:layout_weight="1"
            android:hint="輸入聊天" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發送" />
    </LinearLayout>
</LinearLayout>

         聊天界面中消息接收和消息發送都需要及時更新列表。

import android.app.Activity;
import	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原文地址: "http://www.jianshu.com/p/5358f587af38" Images.xcassets在app打包後,以Assets.car文件的形式出現在bundle中。其作用在於: 自動識別@2x,@3x圖片,對內容相同但解析度不同的圖片統一管理。 可以對圖片進行剪裁和拉伸處 ...
  • 前言 學習本系列內容需要具備一定 HTML 開發基礎,沒有基礎的朋友可以先轉至 "HTML快速入門(一)" 學習 本人接觸 React Native 時間並不是特別長,所以對其中的內容和性質瞭解可能會有所偏差,在學習中如果有錯會及時修改內容,也歡迎萬能的朋友們批評指出,謝謝 文章第一版出自簡書,如果 ...
  • 前言: 項目APP有時候會出現Crash,然後就是彈出系統強制退出的對話框,點擊關閉APP。 有的APP進行了處理,會發現,當程式出現異常的時候,會Toast一個提示“程式出現異常,3秒後將退出程式”。3秒後即關閉程式而不再顯示強制關閉的對話框。 那麼它們是如何處理沒有try-catch 捕獲到的異 ...
  • ➠更多技術乾貨請戳:聽雲博客 前言 最近看 ObjC的runtime 是怎麼實現 +load 鉤子函數的實現。進而引申分析了 dyld 處理 Mach-O 的這部分機制。 1.簡單分析 Mach-O 在dyld 中是如何被載入到記憶體中的; 2.分析了 +load 的 特殊載入時機; + load 上 ...
  • 我們的APP要想吸引用戶,就要把UI(臉蛋)搞漂亮一點。畢竟好的外貌是增進人際關係的第一步,我們程式員看到一個APP時,第一眼就是看這個軟體的功能,不去關心界面是否漂亮,看到好的程式會說“我cao!這個功能寫得很NB”。兩者都很重要 ...
  • Notification是在你的應用常規界面之外展示的消息。當app讓系統發送一個消息的時候,消息首先以圖表的形式顯示在通知欄。要查看消息的詳情需要進入通知抽屜(notificationdrawer)中查看。通知欄和通知抽屜 (notificationdrawer)都是系統層面控制的,你可以隨時查看 ...
  • 一、runtime簡介 RunTime簡稱運行時。OC就是 ,也就是在運行時候的一些機制,其中最主要的是消息機制。 對於C語言, 。 對於OC的函數,屬於 ,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。 事實證明: 在編譯階段,OC可以 ,即使 ...
  • 說起tableView的自動計算行高,真的是不想再提了,寫了不知道幾百遍了。可就是這麼一個小玩意兒,把我給難的不行不行的,眼看都要沒頭髮了。 1、設置tableView的預估行高和行高為自動計算 2、設置cell的contentView的底部約束和最下麵一個控制項的底部約束對齊 3、看、看、看,錯誤來 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...