package com.qnear.qnearfe; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import org.qjson.JSONObject; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.Date; import java.text.SimpleDateFormat; public class MsgActivity extends AppCompatActivity { EditText et_srvAddr; // 服務端地址 EditText et_srvPort; // 服務端端口 EditText et_sendMsg; // 要發送給夥伴的消息 EditText et_userName; // 咱的名稱 ListView lv_msgList; // 所有的消息列表控件 List<String> msgList = new ArrayList<String>(); // 代表消息內容的列表 ArrayAdapter<String> adapter; SckCln cln; // 網絡通信類 String[] recentDstList; // 最近的消息目標用戶列表 final SimpleDateFormat sdf=new SimpleDateFormat("HH:mm");//時間顯示格式 // 顯示消息 public void showMsg(String arg_msg){ // 不顯示空的消息 if(arg_msg == null || arg_msg.isEmpty()){return;} //將消息添加到消息內容的列表 msgList.add(arg_msg); //notifyDataSetChanged方法通過一個外部的方法控制如果適配器的內容改變時需要強制調用getView來刷新每個Item的內容,可以實現動態的刷新列表的功能。、 //adapter適配器,實現動態的刷新列表 adapter.notifyDataSetChanged(); //列表刷新之後,自動滑動到最後一條。 lv_msgList.smoothScrollToPosition(msgList.size()); } // 接收來自網絡的消息,以顯示到UI主線程(屏幕)上 //只有一個線程體 Handler msgHandler = new Handler() { @Override public void handleMessage(Message msg) { // msg.Data["msg"]代表消息內容本身 //獲取從服務端得到的message儲存到peerMsg String peerMsg = msg.getData().getString("msg"); //消息爲空,本地顯示“got empty/null message” showMsg("顯示收到的完整消息:"+peerMsg); if(peerMsg == null || peerMsg.isEmpty() || peerMsg.length() <= 0) { showMsg("got empty/null message"); return; } //當消息不爲空的時候,對消息的處理 //程序員debug使用,用於確定出錯位置,(不太確定) //LOG是廣泛使用的用來記錄程序執行過程的機制,它既可以用於程序調試,也可以用於產品運營中的事件記錄; Log.d("QNearFE", peerMsg); //建立一個String 用於消息展示 String dstMsg=""; try{ //json協議的處理 JSONObject m = new JSONObject(peerMsg); dstMsg = m.getString("src") + ": " + m.getString("msg"); }catch(Exception e){ dstMsg = peerMsg; } //消息處理完後,顯示最終的消息內容 showMsg("處理後的信息:"+dstMsg); // showMsg("不是吧"); } }; // 連接服務端 void connectSrv() { //已經連接 if (cln != null && cln.isAlive()) { showMsg("Already connected."); return; } //未連接,建立連接進行通訊 //參數分析,(String 服務器ip地址,int 端口號,Handlder UI處理者) //et_srvAddr,et_srvPort應該是通過界面輸入之後獲取 //將et_srvPort獲取到的字符串通過Integer.parseInt()轉化爲int類型 //msgHandler 前面有定義過 cln = new SckCln(et_srvAddr.getText().toString(), Integer.parseInt(et_srvPort.getText().toString()), msgHandler); //啓動線程 cln.start(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_msg); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); // 建立界面元素與變量的對應關係,以便在程序中訪問 et_srvPort = (EditText) findViewById(R.id.et_srvPort); et_srvAddr = (EditText) findViewById(R.id.et_srvAddr); et_sendMsg = (EditText) findViewById(R.id.et_sendMsg); lv_msgList = (ListView) findViewById(R.id.lv_msgList); et_userName=(EditText)findViewById(R.id.et_userName); adapter = new ArrayAdapter<String>(this, R.layout.msg_item_view, R.id.receivedMsg, msgList); lv_msgList.setAdapter(adapter); // 按下後連接服務端 Button btn_connect = (Button) findViewById(R.id.btn_connect); btn_connect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { connectSrv(); } }); et_sendMsg.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // 用戶在輸入時按下“完成”、“回車/換行/隱藏輸入框"時進行消息處理 if ((actionId != EditorInfo.IME_ACTION_DONE) && (event.getKeyCode() != KeyEvent.KEYCODE_ENTER) && (event.getAction() != KeyEvent.ACTION_DOWN)) { et_sendMsg.getText().clear(); return false; } // if (cln == null || !cln.isAlive() || !cln.bConnected) { // showMsg("please connect to server!"); // return false; // } if (cln == null || !cln.isAlive() || !cln.bConnected) { if(cln.status == Status.DISCONNECTED){ showMsg("please connect to server!"); } if(cln.status == Status.CONNECTING){ showMsg("Still in connecting with server!"); } return false; } // String msg = et_sendMsg.getText().toString(); // if (msg.isEmpty() || msg.length() <= 0) { // return false; // } //獲取從界面輸入的信息 String msg = et_sendMsg.getText().toString(); if (msg == null || msg.isEmpty() || msg.length() <= 0) { return true; } // 對消息進行格式整理,以減少處理難度 msg = msg.replace(' ', ' '); msg = msg.replace(',', ','); msg = msg.replaceAll(", ", ","); msg = msg.replaceAll(" ,", ","); msg = msg.replaceAll("@ ", "@"); msg = msg.replaceAll(" @", "@"); msg = msg.replaceAll(" @ ", "@"); // @表示發消息給誰,可以是一個或多個,用","分隔 int toPos = msg.indexOf("@"); int msgPos = msg.indexOf(" "); String uMsg = ""; String[] dstList = null; if (toPos == 0 && msgPos > 0) { // 有目標,有內容 dstList = msg.substring(1, msgPos).split(","); uMsg = msg.substring(msgPos + 1); } else if (toPos < 0) { // 只有內容 uMsg = msg; } else {// 其實這裏有BUG,如1234@tom this 就會進到這個分支 showMsg("@遊戲出現bug"); return false; } // 如果無消息目標,則使用上次的消息目標,聰明吧! if (dstList == null) { dstList = recentDstList; } // if (dstList == null) { // showMsg("Please speicify msg receiver."); // return false; // } else { // recentDstList = dstList; // } if (dstList == null) { showMsg("Please speicify msg receiver."); return true; } else { recentDstList = dstList; } // 代表你自己,用戶名 String src = et_userName.getText().toString(); String dstMsg = marshalMsg(src,dstList,uMsg); showMsg(dstMsg); if(!dstMsg.substring(0,1).equals("{")){ showMsg(dstMsg); return false; } cln.sendMsg(dstMsg); //清空消息條 et_sendMsg.getText().clear(); String dst=""; if (dstList != null) { for (String d : dstList) { dst = ", " + d; } dst = dst.substring(1,dst.length()); } showMsg(uMsg); return true; } }); // 應用啓動後,立即連接服務端 //connectSrv(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_msg, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } public enum Status{ DISCONNECTED // 斷開連接/未連接 ,CONNECTING // 正在連接 ,CONNECTED // 已連接到服務端 }; // If the first return char is "{" then it is a successfully marshal else it is a failed marshal. public static String marshalMsg(String arg_src, String[] arg_dstList, String arg_msg){ JSONObject qNearMsg = new JSONObject(); try { qNearMsg.put("S", 142857);// 協議簽名 qNearMsg.put("L", "0x12345678");// 協議消息包長度 if (!arg_src.isEmpty()) { qNearMsg.put("src", arg_src); } qNearMsg.put("dst", arg_dstList); qNearMsg.put("msg", arg_msg); String s = qNearMsg.toString(); // 生成消息長度,這是個技巧 String sLen = String.format("0x%08x", s.length()); return s.replaceAll("0x12345678", sLen); } catch (Exception e) { return e.getMessage(); } } // 代表與服務端通信的類,以線程方式運行 class SckCln extends Thread { Handler msgHandler;// UI接收消息處理對象 public boolean bRunning = true;// 如果期望停止它,則設置該值爲FALSE public boolean bConnected = false;// 是否已經連接上了,FALSE就是未連接 public Status status = Status.DISCONNECTED;// 默認狀態是未連接 String srvName = "192.168.XXX.1";// 默認的服務端地址 int srvPort = 6666; // 默認的是服務端端口 //構造函數 public SckCln(String arg_srvName, int arg_port, Handler arg_msgHandler) { if (arg_srvName == null || arg_srvName.isEmpty() || arg_srvName.length() <= 0) { throw new RuntimeException("invalid srvName"); } if (arg_port <= 0) { throw new RuntimeException("invalid port"); } if (arg_msgHandler == null) { throw new RuntimeException("arg_msgReceiver is null."); } srvPort = arg_port; srvName = arg_srvName; msgHandler = arg_msgHandler; //srvPort->服務器監聽的端口號,srvName->服務器的ip地址,msgHandler->UI接受消息處理對象 } //定義套接字,(橋樑) Socket sckCln; //定義輸入流 InputStream sckIn; //定義輸出流 OutputStream sckOut; // 發送消息給UI線程 void msgToUI(int arg_what, String arg_msg) { //建立一個Message對象 Message m = Message.obtain(); //what? m.what = arg_what; //類似是管道,儲存信息 Bundle data = new Bundle(); //將信息儲存到msg,此後可以通過調用"msg"獲得信息 data.putString("msg", arg_msg); //將data儲存到m消息對象中 m.setData(data); //將消息發送到消息處理“中心”UI msgHandler.sendMessage(m); } // 讓UI線程來調用,以向服務端發消息 public boolean sendMsg(String arg_msg) { //未連接服務器 if (!bConnected) { msgToUI(4020, "disconnected with server."); return false; } try{ //黑框顯示“try send msg: xxx to server” System.out.println("try send msg: " + arg_msg + " to server."); //msgToUI(5,"發送了消息"); //輸出信息arg_msg sckOut.write(arg_msg.getBytes("UTF-8")); //刷新,輸送到服務器 sckOut.flush(); }catch (Exception e){ System.out.println(e.getMessage()); } return true; } @Override //線程體執行函數 public void run() { try { //一開始處於未連接狀態 bConnected = false; //正在連接 status = Status.CONNECTING; //正在連接的信息顯示到UI(手機屏幕上) msgToUI(4020, "connecting to server " + srvName + ":" + srvPort); //生成socket的實例對象sckCln sckCln = new Socket(); //sckCln連接服務器 //new InetSocketAddress()生成地址格式 //2500代表最多等待2.5秒 sckCln.connect(new InetSocketAddress(srvName,srvPort), 2500); //狀態爲已連接 status = Status.CONNECTED; //實例化輸入流 sckIn = sckCln.getInputStream(); //實例化輸出流 sckOut = sckCln.getOutputStream(); //在UI上顯示已經連接到服務器 msgToUI(4030, "connected to server: " + srvName + ":" + srvPort); //表示已經連接 bConnected = true; //消息緩衝區大小4KB byte[] buf = new byte[4*1024]; //代表消息長度 int readLen = 0; //循環接聽來自服務端的信息 do { //獲取信息長度 readLen = sckIn.read(buf); //把字符數組buf從0到readLen的字符作爲字符串輸入,生成string類型,賦值給msg //編碼格式爲“UTF-8” String msg = new String(buf,0,readLen,"UTF-8"); // msgToUI(1000, msg); } while (bRunning); } catch (Exception e) { status = Status.DISCONNECTED; msgToUI(4040, e.getMessage()); } } } }
andriod客戶端
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.