前几天学习了 Android 下 Socket 编程,由于个人是刚开始学习 Android 相应的知识。所以特意将学习中的代码与过程,写成 BLOG,如:http://blog.csdn.net/91program/article/details/39177401
学习 Socket 编程是有目的的,需要完成在手机与 PC 之间的通讯。通讯的内容是将手机上播放的 MP3 信息,通过 Socket 传输到 PC 端。
在参考网上相关 Socket 的文章后,基本上完成了 Socket 功能。所以就继续学习 Android 下音乐播放器的实现。在实现音乐播放器过程中,发现由于音乐播放器至少要有播放列表和正在播放两个 Activity,这样问题就来了:
(1). Socket 只是在第一个 Activity 中实现了,当这个 Activity 活动时没有问题。但此 Activity 非活动时,不能处理 Socket。
(2). 当反复进入第一个 Activity 时,会出现 Socket 初始化报错的问题。出现这样的错误,是由于 Sokcet 的初始化放在第一个 Activity 的 onCreate 中。
由于在做音乐播放器时使用了 Service,所以想到用 Serivce 来处理 Socket 应该没有问题。但是否有其它的方法呢?由于个人是刚刚接触 Android 编程,就不能确定这个问题了!
在 CSDN Android 论坛提问,帖子:http://bbs.csdn.net/topics/390884423。得到的答案是:(1) Serivce; (2) 也可以更改activity的启动方式,让串口不重复创建。显然,第二种方法还没有接触过。采用第一种 Serivce 来实现更可靠一些。
首先,实现 Socket Service。
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.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; import android.util.Log; public class SocketService extends Service { private Socket clientSocket = null; private ServerSocket mServerSocket = null; private SocketAcceptThread socketAcceptThread = null; private SocketReceiveThread socketReceiveThread = null; private SocketReceiver socketReceiver; public static final String SOCKER_ACTION = "com.jia.Socket.Control"; public static final String SOCKER_RCV = "com.jia.Socket.ReceiveStr"; private boolean stop = true; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onCreate() { super.onCreate(); Log.d("service", "socket service created"); socketReceiver = new SocketReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(SOCKER_ACTION); registerReceiver(socketReceiver, filter); socketAcceptThread = new SocketAcceptThread(); // 开启 Socket 监听线程 socketAcceptThread.start(); } @Override public void onStart(Intent intent, int startId) { Log.d("service", "socket service start"); } @Override public void onDestroy() { Log.d("service", "socket service destroy!"); } public class SocketReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(action.equals(SOCKER_ACTION)) { String sub_action = intent.getExtras().getString("ACTION"); if(sub_action.equals("reconnect")) { Log.d("service", "socket service: reconnect."); socketAcceptThread = new SocketAcceptThread(); // 开启 Socket 监听线程 socketAcceptThread.start(); } } } } private class SocketAcceptThread extends Thread { @Override public void run() { Log.d("service", "socket service - SocketAcceptThread::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(); } socketReceiveThread = new SocketReceiveThread(clientSocket); stop = false; // 开启接收线程 socketReceiveThread.start(); Intent sendIntent = new Intent(SOCKER_RCV); sendIntent.putExtra("action", "ClientIP"); sendIntent.putExtra("content", clientSocket.getInetAddress().getHostAddress()); // 发送广播,将被Activity组件中的BroadcastReceiver接收到 sendBroadcast(sendIntent); } } private class SocketReceiveThread extends Thread { private InputStream mInputStream = null; private byte[] buf; private String str = null; Socket sUsed; SocketReceiveThread(Socket s) { Log.d("service", "socket service - SocketReceiveThread"); try { // 获得输入流 this.mInputStream = s.getInputStream(); sUsed = s; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { Log.d("service", "socket service - SocketReceiveThread::run"); while((!stop) && (!mServerSocket.isClosed())) { this.buf = new byte[2048]; // 读取输入的数据(阻塞读) 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(); } Intent sendIntent = new Intent(SOCKER_RCV); sendIntent.putExtra("action", "RcvStr"); sendIntent.putExtra("content", this.str); // 发送广播,将被Activity组件中的BroadcastReceiver接收到 sendBroadcast(sendIntent); } } } }
在每个 Activity 中处理 SOCKER_RCV action,以响应 Socket 状态的变化和接收到数据。
Service 与 Activity 之间通讯需要使用到广播: Broadcast。
(1) 在 Activity 中定义全局的变量,如下:
1 public static final String SOCKER_ACTION = "com.jia.Socket.Control"; 2 public static final String SOCKER_RCV = "com.jia.Socket.ReceiveStr"; 3 4 SocketReceiver socketReceiver
(2) 在 Activity 的 onCreate 中注册广播和启动 Socket Service,如下:
1 socketReceiver = new SocketReceiver(); 2 IntentFilter socketIntentFilter = new IntentFilter(); 3 socketIntentFilter.addAction(SOCKER_RCV); 4 registerReceiver(socketReceiver,socketIntentFilter); 5 6 Intent socketIntent = new Intent(); 7 socketIntent.setClass(MainActivity.this, SocketService.class); 8 startService(socketIntent); // 启动 Socket 服务
(3) SocketReceiver 是继承自 BroadcastReceiver 的类,实现如下:
1 public class SocketReceiver extends BroadcastReceiver { 2 3 4 @Override 5 public void onReceive(Context context, Intent intent) { 6 // TODO Auto-generated method stub 7 String action = intent.getAction(); 8 if(action.equals(SOCKER_RCV)) { 9 String url = intent.getExtras().getString("action"); 10 if(url.equals("ClientIP")) { 11 String strIP = intent.getExtras().getString("content"); 12 } 13 else if(url.equals("RcvStr")) { 14 String strContent = intent.getExtras().getString("content"); 15 } 16 else if(url.equals("Disconnect")) { 17 String strContent = intent.getExtras().getString("content"); 18 } 19 } 20 } 21 }
(4) Socket 功能实现后,测试时发现客户端(也就是 PC 端)断开时手机端未检测到 Socket 连接断开。
以前使用 WinCE 时,Socket(TCP) 断开时,无论是客户端、还是服务器都可以检测到 TCP 断开的事件,并处理。但 Android 下的 Socket 编程机制竟然没有这个东东。
一,测试时发现当 PC 端断开后,手机端的服务程序在执行到下面的代码段时不会阻塞,且函数的返回值是: -1。
二,在网上查找发现这个问题是 Android 下 Socket 都有的问题,可以通过发心跳包来处理。
所以将下面这段代码:
1 // 读取输入的数据(阻塞读) 2 try { 3 this.mInputStream.read(buf); 4 } catch (IOException e1) { 5 // TODO Auto-generated catch block 6 e1.printStackTrace(); 7 } 8 修改为如下的代码: 9 try { 10 int length = this.mInputStream.read(buf); 11 if(-1 == length) { 12 try { 13 sUsed.sendUrgentData(0xff); 14 } 15 catch(Exception ex) { 16 // 链接已断开 17 Log.v("service", "disconnect!!!"); 18 stop = true; 19 if(null != mServerSocket) { 20 mServerSocket.close(); 21 } 22 23 24 Intent sendIntent = new Intent(SOCKER_RCV); 25 sendIntent.putExtra("action", "Disconnect"); 26 sendIntent.putExtra("content", "read is -1 & Urgent Exception!"); 27 sendBroadcast(sendIntent); 28 29 30 continue; 31 } 32 } 33 } catch (IOException e1) { 34 // TODO Auto-generated catch block 35 e1.printStackTrace(); 36 }