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); } //-------------------------定义一个enum枚举类型------------------------------ public enum Status{ DISCONNECTED // 断开连接/未连接 ,CONNECTING // 正在连接 ,CONNECTED // 已连接到服务端 }; //-----------------------------JSON协议进行打包MarshalMsg----------------------------------------- // 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.191.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;//srvPort->服务器监听的端口号 srvName = arg_srvName;//srvName->服务器的ip地址 msgHandler = arg_msgHandler;//msgHandler->UI接受消息处理对象 } Socket sckCln;//定义套接字,(桥梁) InputStream sckIn;//定义输入流 OutputStream sckOut;//定义输出流 // -------------------------------发送消息给UI线程---------------------------------------------- void msgToUI(int arg_what, String arg_msg) { Message m = Message.obtain();//建立一个Message对象 m.what = arg_what;//what? Bundle data = new Bundle(); //类似是管道,储存信息 data.putString("msg", arg_msg);//将信息储存到msg,此后可以通过调用"msg"获得信息 m.setData(data);//将data储存到m消息对象中 msgHandler.sendMessage(m);//将消息发送到消息处理“中心”UI } // -------------------------让UI线程来调用,以向服务端发消息------------------------------------ public boolean sendMsg(String arg_msg) { //未连接服务器 if (!bConnected) { msgToUI(4020, "disconnected with server."); return false; } try{ System.out.println("try send msg: " + arg_msg + " to server.");//黑框显示“try send msg: xxx to server” sckOut.write(arg_msg.getBytes("UTF-8")); //输出信息arg_msg sckOut.flush(); //刷新,输送到服务器 }catch (Exception e){ System.out.println(e.getMessage()); msgToUI(4041, e.getMessage());//自己添加 } return true; } @Override //-----------------------------------线程体执行函数--------------------------------------------- public void run() { try { bConnected = false;//一开始处于未连接状态 status = Status.CONNECTING;//正在连接 msgToUI(4020, "connecting to server " + srvName + ":" + srvPort);//正在连接的信息显示到UI(手机屏幕上) sckCln = new Socket();//生成socket的实例对象sckCln //---------------------------sckCln连接服务器------------------------ //new InetSocketAddress()生成地址格式 //2500代表最多等待2.5秒 sckCln.connect(new InetSocketAddress(srvName,srvPort), 2500); status = Status.CONNECTED;//状态为已连接 sckIn = sckCln.getInputStream();//实例化输入流 sckOut = sckCln.getOutputStream();//实例化输出流 msgToUI(4030, "connected to server: " + srvName + ":" + srvPort);//在UI上显示已经连接到服务器 bConnected = true;//表示已经连接 byte[] buf = new byte[4*1024];//消息缓冲区大小4KB int readLen = 0;//代表消息长度 //-----------------------循环接听来自服务端的信息---------------------- do { readLen = sckIn.read(buf);//获取信息长度 String msg = new String(buf,0,readLen,"UTF-8"); //把字符数组buf从0到readLen的字符作为字符串输入,生成string类型,赋值给msg//编码格式为“UTF-8” msgToUI(1000, msg);//将消息发送到UI上 } while (bRunning); } catch (Exception e){ status = Status.DISCONNECTED; msgToUI(4040, e.getMessage()); //发送错误的信息 } } } }
andriod 端
最新推荐文章于 2017-12-09 19:27:17 发布