編輯:關於Android編程
翻譯自http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-2/
在上一篇文章中我們介紹了web sockets,搭建好了web環境,這篇文章我們開始安卓app的開發。同web應用一樣,有兩個屏幕,第一個是輸入名字,第二個就是顯示和發送消息。OK,我們這次的開發環境依然是Eclipse IDE.
首先定義一下我們所用到的顏色res ? values ? colors.xml
#3cb879 #e8e8e8 #82e783 #2b2b2b #434343 #ffffff #5eb964 #e5e7eb #a1a1a1 #1e6258 #e8e8e8 #626262 #777777
再定義我們所用到的字符串res ? values ? strings.xml
WebMobileGroupChat (Android WebSockets Chat App) By Ravi Tamada www.androidhive.info Enter your name JOIN Send
再增加樣式文件res ? values ? styles.xml
下面這個布局文件是第一個屏幕,讓用戶輸入用戶名:
下面是對應的Activity,很簡單,就是傳遞數據
NameActivity.java
package info.androidhive.webgroupchat; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class NameActivity extends Activity { private Button btnJoin; private EditText txtName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_name); btnJoin = (Button) findViewById(R.id.btnJoin); txtName = (EditText) findViewById(R.id.name); // Hiding the action bar getActionBar().hide(); btnJoin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (txtName.getText().toString().trim().length() > 0) { String name = txtName.getText().toString().trim(); Intent intent = new Intent(NameActivity.this, MainActivity.class); intent.putExtra("name", name); startActivity(intent); } else { Toast.makeText(getApplicationContext(), "Please enter your name", Toast.LENGTH_LONG).show(); } } }); } }
不要忘記在清單文件中加上網絡權限
在做完這些之後你的app應該是這樣子
在實現sockets之前,我們還需要定義一些資源,用來顯示聊天界面
下載background圖片放到drawable目錄下。
定義如下三個drawable文件,這些用作聊天的背景tile_bg.xml, bg_msg_from.xml and bg_msg_you.xml
tile_bg.xml
bg_msg_from.xml
bg_msg_you.xml
然後我們在定義兩個布局文件,分別是聊天的條目(自己的和別人的)
list_item_message_left.xml
list_item_message_right.xml
接下來的這個布局文件就是我們聊天的主界面
activity_main.xml
接下來是兩個幫助類,第一個Utils類有兩個功能,第一個是存儲Session id,第二個就是把消息轉換成一個JSON字符串,如下
Utils.java package info.androidhive.webgroupchat.other; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; public class Utils { private Context context; private SharedPreferences sharedPref; private static final String KEY_SHARED_PREF = "ANDROID_WEB_CHAT"; private static final int KEY_MODE_PRIVATE = 0; private static final String KEY_SESSION_ID = "sessionId", FLAG_MESSAGE = "message"; public Utils(Context context) { this.context = context; sharedPref = this.context.getSharedPreferences(KEY_SHARED_PREF, KEY_MODE_PRIVATE); } public void storeSessionId(String sessionId) { Editor editor = sharedPref.edit(); editor.putString(KEY_SESSION_ID, sessionId); editor.commit(); } public String getSessionId() { return sharedPref.getString(KEY_SESSION_ID, null); } public String getSendMessageJSON(String message) { String json = null; try { JSONObject jObj = new JSONObject(); jObj.put("flag", FLAG_MESSAGE); jObj.put("sessionId", getSessionId()); jObj.put("message", message); json = jObj.toString(); } catch (JSONException e) { e.printStackTrace(); } return json; } }
下面是JavaBean對象
Message.java package info.androidhive.webgroupchat.other; public class Message { private String fromName, message; private boolean isSelf; public Message() { } public Message(String fromName, String message, boolean isSelf) { this.fromName = fromName; this.message = message; this.isSelf = isSelf; } public String getFromName() { return fromName; } public void setFromName(String fromName) { this.fromName = fromName; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public boolean isSelf() { return isSelf; } public void setSelf(boolean isSelf) { this.isSelf = isSelf; } }
下面這個類是配置對象
WsConfig.java package info.androidhive.webgroupchat.other; public class WsConfig { public static final String URL_WEBSOCKET = "ws://192.168.0.102:8080/WebMobileGroupChatServer/chat?name="; }
下面這個是ListView的適配器,主要就是判斷是自己的消息還是別人的消息,在getView中分別填充
package info.androidhive.webgroupchat; import info.androidhive.webgroupchat.other.Message; import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class MessagesListAdapter extends BaseAdapter { private Context context; private ListmessagesItems; public MessagesListAdapter(Context context, List navDrawerItems) { this.context = context; this.messagesItems = navDrawerItems; } @Override public int getCount() { return messagesItems.size(); } @Override public Object getItem(int position) { return messagesItems.get(position); } @Override public long getItemId(int position) { return position; } @SuppressLint("InflateParams") @Override public View getView(int position, View convertView, ViewGroup parent) { /** * The following list not implemented reusable list items as list items * are showing incorrect data Add the solution if you have one * */ Message m = messagesItems.get(position); LayoutInflater mInflater = (LayoutInflater) context .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); // Identifying the message owner if (messagesItems.get(position).isSelf()) { // message belongs to you, so load the right aligned layout convertView = mInflater.inflate(R.layout.list_item_message_right, null); } else { // message belongs to other person, load the left aligned layout convertView = mInflater.inflate(R.layout.list_item_message_left, null); } TextView lblFrom = (TextView) convertView.findViewById(R.id.lblMsgFrom); TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg); txtMsg.setText(m.getMessage()); lblFrom.setText(m.getFromName()); return convertView; } }
下載android websockets library,感謝 Koush大神
將下載的代碼導入到eclipse中,並且在自己的項目中引用它
開始最重要的了。。。。
同js代碼作為sockets客戶端類似,WebSocketClient 也有一些回調函數,onConnect, onMessage and onDisconnect.
parseMessage() 函數用作解析從server中獲得的Json字符串
在parseMessage()方法中,json的目的有flag表示
當新的消息收到時,要調用adapter.notifyDataSetChanged() 方法去更新列表
sendMessageToServer()發送到服務器
playBeep() 播放聲音
package info.androidhive.webgroupchat; import info.androidhive.webgroupchat.other.Message; import info.androidhive.webgroupchat.other.Utils; import info.androidhive.webgroupchat.other.WsConfig; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.Intent; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; import com.codebutler.android_websockets.WebSocketClient; public class MainActivity extends Activity { // LogCat tag private static final String TAG = MainActivity.class.getSimpleName(); private Button btnSend; private EditText inputMsg; private WebSocketClient client; // Chat messages list adapter private MessagesListAdapter adapter; private ListlistMessages; private ListView listViewMessages; private Utils utils; // Client name private String name = null; // JSON flags to identify the kind of JSON response private static final String TAG_SELF = "self", TAG_NEW = "new", TAG_MESSAGE = "message", TAG_EXIT = "exit"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnSend = (Button) findViewById(R.id.btnSend); inputMsg = (EditText) findViewById(R.id.inputMsg); listViewMessages = (ListView) findViewById(R.id.list_view_messages); utils = new Utils(getApplicationContext()); // 從上一個屏幕獲取姓名 Intent i = getIntent(); name = i.getStringExtra("name"); btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Sending message to web socket server sendMessageToServer(utils.getSendMessageJSON(inputMsg.getText() .toString())); // Clearing the input filed once message was sent inputMsg.setText(""); } }); listMessages = new ArrayList (); adapter = new MessagesListAdapter(this, listMessages); listViewMessages.setAdapter(adapter); /** * 創建web sockets客戶端,有如下的回調函數 * */ client = new WebSocketClient(URI.create(WsConfig.URL_WEBSOCKET + URLEncoder.encode(name)), new WebSocketClient.Listener() { @Override public void onConnect() { } /** * 從服務端接受消息 * */ @Override public void onMessage(String message) { Log.d(TAG, String.format("Got string message! %s", message)); parseMessage(message); } @Override public void onMessage(byte[] data) { Log.d(TAG, String.format("Got binary message! %s", bytesToHex(data))); // Message will be in JSON format parseMessage(bytesToHex(data)); } /** * 連接中斷 * */ @Override public void onDisconnect(int code, String reason) { String message = String.format(Locale.US, "Disconnected! Code: %d Reason: %s", code, reason); showToast(message); // clear the session id from shared preferences utils.storeSessionId(null); } @Override public void onError(Exception error) { Log.e(TAG, "Error! : " + error); showToast("Error! : " + error); } }, null); client.connect(); } /** * 發送消息 * */ private void sendMessageToServer(String message) { if (client != null && client.isConnected()) { client.send(message); } } /** * 解析從服務端收到的json 消息的目的由flag字段所指定,flag=self,消息屬於指定的人, * new:新人加入 * 到對話中,message:新的消息,exit:退出 * * * * * */ private void parseMessage(final String msg) { try { JSONObject jObj = new JSONObject(msg); // JSON node 'flag' String flag = jObj.getString("flag"); // 如果是self,json中包含sessionId信息 if (flag.equalsIgnoreCase(TAG_SELF)) { String sessionId = jObj.getString("sessionId"); // Save the session id in shared preferences utils.storeSessionId(sessionId); Log.e(TAG, "Your session id: " + utils.getSessionId()); } else if (flag.equalsIgnoreCase(TAG_NEW)) { // If the flag is 'new', new person joined the room String name = jObj.getString("name"); String message = jObj.getString("message"); // number of people online String onlineCount = jObj.getString("onlineCount"); showToast(name + message + ". Currently " + onlineCount + " people online!"); } else if (flag.equalsIgnoreCase(TAG_MESSAGE)) { // if the flag is 'message', new message received String fromName = name; String message = jObj.getString("message"); String sessionId = jObj.getString("sessionId"); boolean isSelf = true; // Checking if the message was sent by you if (!sessionId.equals(utils.getSessionId())) { fromName = jObj.getString("name"); isSelf = false; } Message m = new Message(fromName, message, isSelf); // 把消息加入到arraylist中 appendMessage(m); } else if (flag.equalsIgnoreCase(TAG_EXIT)) { // If the flag is 'exit', somebody left the conversation String name = jObj.getString("name"); String message = jObj.getString("message"); showToast(name + message); } } catch (JSONException e) { e.printStackTrace(); } } @Override protected void onDestroy() { super.onDestroy(); if(client != null & client.isConnected()){ client.disconnect(); } } /** * 把消息放到listView裡 * */ private void appendMessage(final Message m) { runOnUiThread(new Runnable() { @Override public void run() { listMessages.add(m); adapter.notifyDataSetChanged(); // Playing device's notification playBeep(); } }); } private void showToast(final String message) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } }); } /** * 播放默認的通知聲音 * */ public void playBeep() { try { Uri notification = RingtoneManager .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification); r.play(); } catch (Exception e) { e.printStackTrace(); } } final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } }
最終結果應該是這樣的
小米手環2支持哪些手機?小米手環2准備開放購買了,相比於小米手環一代,新款的小米手環2更值得大家購買,那麼在購買之前,先要了解小米手環2是否支持自己的手機,
掌握什麼是View? View 坐標的基本概念 View的生命周期 如何自定義View什麼是View?android.app.View 就是手機的UI,View 負責繪制
Retrofit提供了5種內置的注解:GET、POST、PUT、DELETE和HEAD,在注解中指定的資源的相對URLurl與參數小總結@GET("users/
Service概念及用途A service is an application component that can perform long-running opera