Socket 编程在Android手机调试过程详解
扫描二维码
随时随地手机看文章
昨天正式开始 Android 编程学习与实践,由于 Android 模拟器在 WinXP 下一直未安装成功,所在将闲置很久的 Android 手机: 联想 A750 找到用于调试。
A750 是 Android 版本是: 2.3.6,在手机 上打开 USB 调试功能后,就可以通过 USB 线与 PC 连接进行调试了。
调试的主要功能是 Socket 通讯,手机做为服务器端。先用 PC 做为客户端。后期的客户端是车机,车机的系统可能是 WinCE 或 Android。
开始之前,先了解了一下 Android 编程的基本知识(后附个人学习记录的知识点),然后学习了关于 Socket 编程的知识。
其中 一个关键的知识点是线程与主进程之间的消息传递机制,主要是线程中的消息传递到主进程。例如:
mHandler.sendMessage(msg);
手机端代码如下(XML就不提供了,很简单,大家看图就知识的):
/*
* 通过 WIFI 网络进行 Socket 通讯成功, 手机的 IP: 172.25.103.4(随个人网络环境变化)
* 测试使用 360 随身 WIFI, PC 机的 IP: 172.25.103.1
* */
package com.jia.leozhengfirstapp; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import android.support.v7.app.ActionBarActivity; import android.annotation.SuppressLint; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends ActionBarActivity { private Socket clientSocket = null; private ServerSocket mServerSocket = null; private Handler mHandler = null; private AcceptThread mAcceptThread = null; private ReceiveThread mReceiveThread = null; private boolean stop = true; private TextView ipText; private TextView rcvText; private TextView ConnectStatusText; @SuppressLint("HandlerLeak") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ipText = (TextView)findViewById(R.id.textView1); rcvText = (TextView)findViewById(R.id.textView3); ConnectStatusText = (TextView)findViewById(R.id.textView4); ConnectStatusText.setText("连接状态: 未连接"); // 消息处理 mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case 0: { String strAddress = (msg.obj).toString(); ipText.setText("IP地址: " + strAddress); Log.v("Leo: IP: ", strAddress); ConnectStatusText.setText("连接状态: 已连接"); // 显示客户端IP // ipTextView.setText((msg.obj).toString()); // 使能发送按钮 // sendButton.setEnabled(true); break; } case 1: { String strRcv = (msg.obj).toString(); rcvText.setText("接收到数据: " + strRcv); Log.v("Leo: Rcv: ", strRcv); // 显示接收到的数据 // mTextView.setText((msg.obj).toString()); break; } } } }; mAcceptThread = new AcceptThread(); // 开启监听线程 mAcceptThread.start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, 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(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } // 显示Toast函数 private void displayToast(String s) { Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); } private class AcceptThread extends Thread { @Override public void run() { try { // 实例化ServerSocket对象并设置端口号为 12589 mServerSocket = new ServerSocket(12589); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { // 等待客户端的连接(阻塞) clientSocket = mServerSocket.accept(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } mReceiveThread = new ReceiveThread(clientSocket); stop = false; // 开启接收线程 mReceiveThread.start(); Message msg = new Message(); msg.what = 0; // 获取客户端IP msg.obj = clientSocket.getInetAddress().getHostAddress(); // 发送消息 mHandler.sendMessage(msg); } } private class ReceiveThread extends Thread { private InputStream mInputStream = null; private byte[] buf; private String str = null; ReceiveThread(Socket s) { try { // 获得输入流 this.mInputStream = s.getInputStream(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { while(!stop) { this.buf = new byte[512]; // 读取输入的数据(阻塞读) try { this.mInputStream.read(buf); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // 字符编码转换 try { this.str = new String(this.buf, "GB2312").trim(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } Message msg = new Message(); msg.what = 1; msg.obj = this.str; // 发送消息 mHandler.sendMessage(msg); } } } }
1) 先通过 WIFI 进行 Socket 通讯
运行后,在手机上显示的未连接界面如下图:
在 PC 上运行类似于 SocketTool.exe 的工具,SocketTool.exe 的显示界面如下:
Socket 连接后,手机上显示的界面如下图:
2) 通过 USB 实现 PC 与手机的通讯
在 PC 上实现一个 Java 小应用,应用的主要代码如下:
try { Runtime.getRuntime().exec("adb forward tcp:12581 tcp:12589"); } catch (IOException e) { e.printStackTrace(); } Socket socket = null; try { InetAddress serverAddr = null; OutputStream outStream = null; byte[] msgBuffer = null; serverAddr = InetAddress.getByName("127.0.0.1"); System.out.println("TCP 1" + "C: Connecting..."); socket = new Socket(serverAddr, 12581); // 12581 是 PC 的端口,已重定向到 Device 的 12589 端口 String message = "PC ADB,send message"; msgBuffer = message.getBytes("GB2312"); outStream = socket.getOutputStream(); outStream.write(msgBuffer); Thread.sleep(10000); } catch (UnknownHostException e1) { System.out.println("TCP 2" + "ERROR: " + e1.toString()); } catch (IOException e2) { System.out.println("TCP 3" + "ERROR: " + e2.toString()); } catch (InterruptedException e3) { // Thread.sleep(1000); 增加的异常处理 e3.printStackTrace(); } finally { try { if (socket != null) { socket.close(); // 关闭时会导致接收到的数据被清空,所以延时 10 秒显示 } } catch (IOException e) { System.out.println("TCP 4" + "ERROR: " + e.toString()); } }
运行后,在手机上显示的界面如下图:
以下是学习中记录的一些个人认为需要掌握的知识点,由于偶是从零开始学习的,所以有经难的朋友们可以直接忽略此部分:
Intent 用法:
Uri myUri = Uri.parse ("http://www.flashwing.net"); Intent openBrowserIntent = new Intent(Intent.ACTION_VIEW ,myUri); startActivity(openBrowserIntent); Intent openWelcomeActivityIntent= new Intent(); openWelcomeActivityIntent.setClass(AndroidStudy_TWO.this,Welcome.class); startActivity(openWelcomeActivityIntent);
从源 Activity 中传递数据
// 数据写入 Intent Intent openWelcomeActivityIntent= new Intent(); Bundle myBundelForName= new Bundle(); myBundelForName.putString("Key_Name",inName.getText().toString()); myBundelForName.putString("Key_Age",inAge.getText().toString()); openWelcomeActivityIntent.putExtras(myBundelForName); openWelcomeActivityIntent.setClass(AndroidBundel.this,Welcome.class); startActivity(openWelcomeActivityIntent);
// 从 Intent 中获取数据
Bundle myBundelForGetName= this.getIntent().getExtras(); String name=myBundelForGetName.getString("Key_Name");
从源请求 Activity 中通过一个 Intent 把一个服务请求传到目标 Activity 中
private Intent toNextIntent; //Intent 成员声明 toNextIntent = new Intent(); //Intent 定义 toNextIntent.setClass(TwoActivityME3.this,SecondActivity3.class); // 设定开启的下一个 Activity startActivityForResult(toNextIntent,REQUEST_ASK); // 开启 Intent 时候 ,把请求码同时传递
在源请求 Activity 中等待 Intent 返回应答结果,通过重载 onActivityResult() 方法
@Override protected void onActivityResult(int requestCode,int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ASK) { if (resultCode == RESULT_CANCELED) { setTitle( "Cancel****" ); } else if (resultCode == RESULT_OK) { showBundle = data.getExtras(); // 从返回的 Intent 中获得 Bundle Name = showBundle.getString("myName"); // 从 bundle 中获得相应数据 text.setText("the name get from the second layout:n" + Name); } } }
* 第一个参数是你开启请求 Intent 时的对应请求码,可以自己定义。
* 第二个参数是目标 Activity 返回的验证结果码
* 第三个参数是目标 Activity 返回的 Intent
目标 Activity 中发送请求结果代码,连同源 Activity 请求的数据一同绑定到 Bundle 中通过 Intent 传回源请求 Activity 中
backIntent = new Intent(); stringBundle = new Bundle(); stringBundle.putString("myName",Name); backIntent.putExtras(stringBundle); setResult(RESULT_OK,backIntent); // 返回 Activity 结果码 finish();
使用服务进行音乐播放
Manifest.xml中的 Service 定义
Service 子类中的 Player
public void onStart(Intent intent, int startId) { super.onStart(intent, startId); player = MediaPlayer.create(this, R.raw.seven_days); player.start(); } public void onDestroy() { super.onDestroy(); player.stop(); }
Activity 中定义 的 Intent开启相应的
startService(new Intent("com.test.service.START_AUDIO_SERVICE")); stopService(new Intent("com.test.service.START_AUDIO_SERVICE"));
DisplayMetrics displaysMetrics= new DisplayMetrics(); //DisplayMetrics 一个描述普通显示信息的结构,例如显示大小、密度、字体尺寸 getWindowManager().getDefaultDisplay().getMetrics(displaysMetrics); //getManager() 获取显示定制窗口的管理器。 // 获取默认显示 Display 对象 // 通过 Display 对象的数据来初始化一个 DisplayMetrics 对象 标题栏/状态栏隐藏 ( 全屏 ) // 隐藏标题 requestWindowFeature(Window.FEATURE_NO_TITLE); // 定义全屏参数 int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN ; // 获得窗口对象 Window myWindow = this.getWindow(); // 设置 Flag 标识 myWindow.setFlags(flag,flag);
Button 按下的处理:
press =(Button)findViewById(R.id.Click_Button);
(1)
press.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } });
(2)
press.setOnClickListener(this); @Override public void onClick(View v) { switch(v.getId()) /* 根据 ID 判断按钮事件 */ { case R.id.Click_Button: { } break; default: break; } }
Toast--Android 专属浮动小提示
(1) 显示文本: Toast.makeText
(2) 显示图像:
/* 创建新 Toast 对象 */ Toast showImageToast= new Toast(this); /* 创建新 ImageView 对象 */ ImageView imageView= new ImageView(this); /* 从资源中获取图片 */ imageView.setImageResource(R.drawable. argon ); /* 设置 Toast 上的 View--(ImageView)*/ showImageToast.setView(imageView); /* 设置 Toast 显示时间 */ showImageToast.setDuration(Toast. LENGTH_LONG ); /* 显示 Toast*/ showImageToast.show();
模拟器调试:
socket = new Socket("10.0.2.2", 12589); // 如果用 localhost 不能成功连接