Socket 編程在Android手機調(diào)試過程詳解
昨天正式開始 Android 編程學(xué)習(xí)與實踐,由于 Android 模擬器在 WinXP 下一直未安裝成功,所在將閑置很久的 Android 手機: 聯(lián)想 A750 找到用于調(diào)試。
A750 是 Android 版本是: 2.3.6,在手機 上打開 USB 調(diào)試功能后,就可以通過 USB 線與 PC 連接進行調(diào)試了。
調(diào)試的主要功能是 Socket 通訊,手機做為服務(wù)器端。先用 PC 做為客戶端。后期的客戶端是車機,車機的系統(tǒng)可能是 WinCE 或 Android。
開始之前,先了解了一下 Android 編程的基本知識(后附個人學(xué)習(xí)記錄的知識點),然后學(xué)習(xí)了關(guān)于 Socket 編程的知識。
其中 一個關(guān)鍵的知識點是線程與主進程之間的消息傳遞機制,主要是線程中的消息傳遞到主進程。例如:
mHandler.sendMessage(msg);
手機端代碼如下(XML就不提供了,很簡單,大家看圖就知識的):
/*
?* 通過 ?WIFI 網(wǎng)絡(luò)進行 Socket 通訊成功, 手機的 IP: 172.25.103.4(隨個人網(wǎng)絡(luò)環(huán)境變化)
?* 測試使用 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("連接狀態(tài):?未連接"); ???????? ????????//?消息處理 ????????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("連接狀態(tài):?已連接"); ????????????????????????//?顯示客戶端IP ????????????????????????//?ipTextView.setText((msg.obj).toString()); ????????????????????????//?使能發(fā)送按鈕 ????????????????????????//?sendButton.setEnabled(true); ????????????????????????break; ????????????????????} ????????????????????case?1: ????????????????????{ ???????????????????? String?strRcv?=?(msg.obj).toString(); ????????????????????????rcvText.setText("接收到數(shù)據(jù):?"?+?strRcv); ????????????????????????Log.v("Leo:?Rcv:?",?strRcv); ????????????????????????//?顯示接收到的數(shù)據(jù) ????????????????????????//?mTextView.setText((msg.obj).toString()); ????????????????????????break; ????????????????????}????????????????? ????????????????}??????????????????????????????????????????? ???????????????? ????????????} ????????}; ???????? ????????mAcceptThread?=?new?AcceptThread(); ????????//?開啟監(jiān)聽線程 ????????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函數(shù) ????private?void?displayToast(String?s) ????{ ????????Toast.makeText(this,?s,?Toast.LENGTH_SHORT).show(); ????} ???? ????private?class?AcceptThread?extends?Thread ????{ ????????@Override ????????public?void?run() ????????{ ????????????try?{ ????????????????//?實例化ServerSocket對象并設(shè)置端口號為?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(); ????????????//?發(fā)送消息 ????????????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]; ???????????????? ????????????????//?讀取輸入的數(shù)據(jù)(阻塞讀) ????????????????try?{ ????????????????????this.mInputStream.read(buf); ????????????????}?catch?(IOException?e1)?{ ????????????????????//?TODO?Auto-generated?catch?block ????????????????????e1.printStackTrace(); ????????????????} ???????????????? ????????????????//?字符編碼轉(zhuǎn)換 ????????????????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; ????????????????//?發(fā)送消息 ????????????????mHandler.sendMessage(msg); ????????????} ????????} ????} }
1) 先通過 WIFI 進行 Socket 通訊
運行后,在手機上顯示的未連接界面如下圖:
在 PC 上運行類似于 SocketTool.exe 的工具,SocketTool.exe 的顯示界面如下:
Socket 連接后,手機上顯示的界面如下圖:
2) 通過 USB 實現(xiàn) PC 與手機的通訊
在 PC 上實現(xiàn)一個 Java 小應(yīng)用,應(yīng)用的主要代碼如下:
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(); //?關(guān)閉時會導(dǎo)致接收到的數(shù)據(jù)被清空,所以延時?10?秒顯示 ????????} ????} ????catch?(IOException?e) ????{ ????????System.out.println("TCP?4"?+?"ERROR:?"?+?e.toString()); ????} }
運行后,在手機上顯示的界面如下圖:
以下是學(xué)習(xí)中記錄的一些個人認為需要掌握的知識點,由于偶是從零開始學(xué)習(xí)的,所以有經(jīng)難的朋友們可以直接忽略此部分:
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 中傳遞數(shù)據(jù)
//?數(shù)據(jù)寫入?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 中獲取數(shù)據(jù)
Bundle?myBundelForGetName=?this.getIntent().getExtras(); String?name=myBundelForGetName.getString("Key_Name");
從源請求 Activity 中通過一個 Intent 把一個服務(wù)請求傳到目標(biāo) Activity 中
private?Intent?toNextIntent;?//Intent?成員聲明 toNextIntent?=?new?Intent();?//Intent?定義 toNextIntent.setClass(TwoActivityME3.this,SecondActivity3.class); //?設(shè)定開啟的下一個?Activity startActivityForResult(toNextIntent,REQUEST_ASK); //?開啟?Intent?時候?,把請求碼同時傳遞
在源請求 Activity 中等待 Intent 返回應(yīng)答結(jié)果,通過重載 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?中獲得相應(yīng)數(shù)據(jù) ??????text.setText("the?name?get?from?the?second?layout:n"?+?Name); ????} ??} }
* 第一個參數(shù)是你開啟請求 Intent 時的對應(yīng)請求碼,可以自己定義。
* 第二個參數(shù)是目標(biāo) Activity 返回的驗證結(jié)果碼
* 第三個參數(shù)是目標(biāo) Activity 返回的 Intent
目標(biāo) Activity 中發(fā)送請求結(jié)果代碼,連同源 Activity 請求的數(shù)據(jù)一同綁定到 Bundle 中通過 Intent 傳回源請求 Activity 中
backIntent?=?new?Intent(); stringBundle?=?new?Bundle(); stringBundle.putString("myName",Name); backIntent.putExtras(stringBundle); setResult(RESULT_OK,backIntent);?//?返回?Activity?結(jié)果碼 finish();
使用服務(wù)進行音樂播放
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開啟相應(yīng)的
startService(new?Intent("com.test.service.START_AUDIO_SERVICE")); stopService(new?Intent("com.test.service.START_AUDIO_SERVICE"));
DisplayMetrics?displaysMetrics=?new?DisplayMetrics(); //DisplayMetrics?一個描述普通顯示信息的結(jié)構(gòu),例如顯示大小、密度、字體尺寸 getWindowManager().getDefaultDisplay().getMetrics(displaysMetrics); //getManager()?獲取顯示定制窗口的管理器。 //?獲取默認顯示?Display?對象 //?通過?Display?對象的數(shù)據(jù)來初始化一個?DisplayMetrics?對象 標(biāo)題欄/狀態(tài)欄隱藏?(?全屏?) //?隱藏標(biāo)題 requestWindowFeature(Window.FEATURE_NO_TITLE); //?定義全屏參數(shù) int?flag?=?WindowManager.LayoutParams.FLAG_FULLSCREEN?; //?獲得窗口對象 Window?myWindow?=?this.getWindow(); //?設(shè)置?Flag?標(biāo)識 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())?/*?根據(jù)?ID?判斷按鈕事件?*/ ??{ ????case?R.id.Click_Button: ????{ ????} ????break; ????default: ????break; ??} }
Toast--Android 專屬浮動小提示
(1) 顯示文本: Toast.makeText
(2) 顯示圖像:?
/*?創(chuàng)建新?Toast?對象?*/ Toast?showImageToast=?new?Toast(this); /*?創(chuàng)建新?ImageView?對象?*/ ImageView?imageView=?new?ImageView(this); /*?從資源中獲取圖片?*/ imageView.setImageResource(R.drawable.?argon?); /*?設(shè)置?Toast?上的?View--(ImageView)*/ showImageToast.setView(imageView); /*?設(shè)置?Toast?顯示時間?*/ showImageToast.setDuration(Toast.?LENGTH_LONG?); /*?顯示?Toast*/ showImageToast.show();
模擬器調(diào)試:
socket?=?new?Socket("10.0.2.2",?12589); //?如果用?localhost?不能成功連接