按计划每周更新一篇技术博文,上周因工作忙而中断,第三篇:《Android基于Socket无线遥控(1)--Socket基本模型搭建》
本例子内容比较多,初步构思分三篇完成,Android系统基于Socket实现无线遥控,可控制另一台Android设备音量增减、亮度调节、方向控制、确认、退出等,本篇主要介绍Socket基本模型搭建,以及无线控制Android系统音量增加,亮度增减。
一、技术介绍
1、Socket介绍
Socket技术实现,软件实现部分有客户端(遥控器)和服务器(显示器),服务器与客户端定制一致的收发消息信息,以致达到需求功能,主要使用步骤如下:
a.服务器通过创建ServerSocket指定一个固定的端口号,以致客户端能够向该端口号发送连接请求;
b.服务器通过accept()方法为下一个传入的连接请求创建Socket实例,如果没有新的连接请求,accept()方法将处于阻塞等待;
c.客户端创建Socket,指定远程地址和端口号,向服务端发送请求;
d.客户端与服务器连接成功后通过InputStream和OutputStream进行数据信息交互以达到无线遥控功能。
通常情况下,服务器端可能会接收多个客户端的请求,此时需要使用循环来接收来自不同客户端的连接请求,主要使用如下代码:
// 创建一个ServerSocket,Ports为端口号 ServerSocket ss = new ServerSocket(Ports); // 采用循环不断接收来自客户端的请求 while(!quit) { //此行代码会阻塞,将一直等待别人的连接 Socket s = ss.accept(); socketList.add(s); //每当客户端连接后启动一条ServerThread线程为该客户端服务 new Thread(new ServerThread(s)).start(); …… }
客户端创建Socket,指定服务端IP地址和端口号,当Socket创建成功后,服务端的ServerSocket的accept()方法会向下执行,此时服务器端和客户端产生一对相互连接的Socket,创建Socket代码如下:
// 创建Socket连接服务器,IP 为IP地址,Ports为端口号 Socket s = new Socket( IP, Ports ); // 客户端启动ClientThread线程不断读取来自服务器的数据 new Thread(new ClientThread(s , handler )).start(); ……
当客户端与服务端产生对应的Socket之后,程序无需再区分服务端与客户端,而是通过各自的Socket进行通讯,主要采用Java IO流来进行数据信息传输,输入输出流方法乳如下:
a.InputStream getInputStream() 返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据,主要使用之一:
…… BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()));…… String content = null; //不断读取Socket输入流中的内容。 while ((content = br .readLine()) != null) { // 对接收到的信息进行对应操作 }
b.OutputStream getOutputStream() 返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据,主要使用之一:
…… OutputStream os = socket.getOutputStream(); String OutputStr = "";…… os.write(OutputStr + "\r\n").getBytes("utf-8"));……
二、编码前预定准则
1、客户端(遥控器)、服务器(显示器)
2、端口:58888 (有效范围0~65535)
3、消息规则
a.客户端 --请求连接--> 服务器
消息:"CN" // CN为connect标志
b.客户端 --方向请求--> 服务器
消息:
"DT" // 向上(direction Top)
"DB" // 向下(bottom )
"DL" // 向左(Left)
"DR" // 向右(Right)
C.客户端 --音量增加、减少-->服务器
消息:
"VO +" // 音量增加(volume)
"VO -" // 音量减少
D.客户端 --亮度增加、减少-->服务器
消息:
"BR +" // 亮度增加(brightness)
"BR -" // 亮度减少
E.客户端 --单击确认-->服务器
消息:
"CL" // 单击确认(Click)
F.客户端 --菜单-->服务器
消息:
"ME" // 取消(Menul)
G.客户端 --返回--> 服务器
消息:
"BA" //退出(Back)
H.客户端 --电源--> 服务器
消息:
"PO" // 电源(Power)
I.客户端 --Home--> 服务器
消息:
"HO" //退出(Home)
三、编码实现
1.页面截图
a.遥控器简单界面
b.服务器端显示界面一
c.服务器端显示界面二
2.客户端(遥控器)主要代码
a.RemoteControlClient.java 遥控器客户端
/** * 遥控器客户端 * */public class RemoteControlClient extends Activity{…… private OutputStream os ; private String IP = "192.168.2.10" ; // 此IP地址根据实际项目修改 private int Ports = 58888; // 端口…… private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 如果消息来自于子线程 if (msg.what == 0x123) { // 将读取的内容追加显示在文本框中 show .append(msg.obj .toString()); } } }; OnClickListener mOnClickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id. send: try { Log. v( "", "Socket ---> input : " + input.getText().toString()); // 将用户在文本框内输入的内容写入网络 os .write((input .getText().toString() + "\r\n").getBytes( "utf-8" )); // 清空input文本框 input .setText("" ); } catch (Exception e) { e.printStackTrace(); } break ; case R.id. volume_add_btn: input .setText("VO +" ); break ; case R.id. volume_reduced_btn: input .setText("VO -" ); break ; case R.id. brightness_add_btn: input .setText("BR +" ); break ; case R.id. brightness_reduced_btn: input .setText("BR -" ); break ; case R.id. direction_top_btn: input .setText("DT" ); break ; case R.id. direction_bottom_btn: input .setText("DB" ); break ; case R.id. direction_left_btn: input .setText("DL" ); break ; case R.id. direction_right_btn: input .setText("DR" ); break ; case R.id. click_btn: input .setText("CL" ); break ; case R.id. back_btn: input .setText("BA" ); break ; case R.id. menu_btn: input .setText("ME" ); break ; case R.id. power_btn: input .setText("PO" ); break ; case R.id. home_btn: input .setText("HO" ); break ; case R.id. connect_btn: try { IP = ipInput .getText().toString(); Log. v( "", " --> IP : " + IP ); // 创建Socket连接服务器 Socket s = new Socket(IP , Ports); // 客户端启动ClientThread线程不断读取来自服务器的数据 new Thread( new ClientThread(s, handler)).start(); os = s.getOutputStream(); Toast. makeText( context, getString(R.string. con_success ), Toast. LENGTH_LONG).show(); reContentView(); } catch (Exception e) { e.printStackTrace(); Toast. makeText( context, getString(R.string. con_failed ), Toast. LENGTH_LONG).show(); } break ; } } };}
b. ClientThread.java 不断读取来自服务端的数据信息
public class ClientThread implements Runnable{ //该线程负责处理的Socket private Socket s; private Handler handler; //该线程所处理的Socket所对应的输入流 BufferedReader br = null; public ClientThread(Socket s , Handler handler) throws IOException { this.s = s; this.handler = handler; br = new BufferedReader(new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; //不断读取Socket输入流中的内容。 while ((content = br.readLine()) != null) { // 每当读到来自服务器的数据之后,发送消息通知程序界面显示该数据 Message msg = new Message(); msg.what = 0x123; msg.obj = content; handler.sendMessage(msg); } } catch (Exception e) { e.printStackTrace(); } }}
c.Manifest.xml(配置文件)需要添加权限
<uses-permission android:name="android.permission.INTERNET"/>
3.服务端(显示器)主要代码(ps. 部分功能未完成,下篇更新)
a.MainActivity.java 服务端(显示器)
public class MainActivity extends Activity { private Button getIpBtn; private static TextView showTips; private TextView showIp; private WifiAdmin wifi; public static MainActivity mActivity; private ToggleButton startTB; private Intent mIntent; private int ip; public static SystemManager systemManager = null; public static final int CONN_SUCCESS = 1; public static final int CONN_ERROR = 2; public static final int SET_BRIGHTNESS = 3; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mActivity = this; wifi = new WifiAdmin(getApplicationContext()); ip = wifi.GetIPAddress(); systemManager = new SystemManager(); mIntent = new Intent(); mIntent.setAction("com.example.socket.SERVICE"); getIpBtn = (Button)findViewById(R.id.get_ip_btn); showTips = (TextView)findViewById(R.id.tips_tv); showIp = (TextView)findViewById(R.id.ip_tv); startTB = (ToggleButton)findViewById(R.id.socketStart); getIpBtn.setOnClickListener(mOnClickListener); startTB.setOnCheckedChangeListener(mOnCheckedChangeListener); } private OnClickListener mOnClickListener = new OnClickListener() { @Override public void onClick(View v) { if(v == getIpBtn){ ip = wifi.GetIPAddress(); String IP = WifiAdmin.longToIP(ip); showIp.setText("IP : " + IP); } } }; // 连接 & 断开 Socket服务 private OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(buttonView == startTB){ if(isChecked){ startService(mIntent); }else{ stopService(mIntent); mHandler.sendEmptyMessage(CONN_ERROR); } } } }; public static Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case CONN_SUCCESS: String socketListStr=""; int num = 1; for(Socket socket : SocketService.socketList) { socketListStr += "\n------------------\n客户端 " + num++ + " : " +socket; } showTips.setText( mActivity.getString(R.string.tips_conn_success) + socketListStr); break; case CONN_ERROR: showTips.setText(R.string.tips_conn_wifi); break; case SET_BRIGHTNESS: float brightness = (Float) msg.obj; SystemManager.setBrightness(MainActivity.mActivity,brightness); SystemManager.saveBrightness(MainActivity.mActivity.getContentResolver(),brightness); break; } } };}
b.SocketService.java 服务端创建SocketService不断接收客户端连接请求
/** * 负责接收新的连接 * 以及控制系统的功能 * @author zzp * */public class SocketService extends Service { //定义保存所有Socket的ArrayList public static ArrayList<Socket> socketList = new ArrayList<Socket>(); private boolean quit = false; private ServerSocket ss; private int Ports = 58888; // 端口 private static SocketService mService; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onCreate() { super.onCreate(); Log.v("", "SocketService onCreate"); mService = this; new Thread(mSocketRunnable).start(); } /** * 此线程等待新的客户端加入 */ Runnable mSocketRunnable = new Runnable() { @Override public void run() { try { ss = new ServerSocket(Ports); while(!quit) { //此行代码会阻塞,将一直等待别人的连接 Socket s = ss.accept(); Log.v("", "Socket ---> s : " + s.toString()); socketList.add(s); //每当客户端连接后启动一条ServerThread线程为该客户端服务 new Thread(new ServerThread(s)).start(); MainActivity.mHandler.sendEmptyMessage(MainActivity.CONN_SUCCESS); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e){ e.printStackTrace(); } } }; @Override public void onDestroy() { super.onDestroy(); quit = true; SocketService.socketList.clear(); try { ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.v("", "SocketService onDestroy"); } public static final int SET_BRIGHTNESS = 1; public static Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SET_BRIGHTNESS: // 取得当前运行的Activity名字 ActivityManager am = (ActivityManager)mService.getSystemService(ACTIVITY_SERVICE); ComponentName cn = am.getRunningTasks(1).get(0).topActivity; Log.d("", "pkg:"+cn.getPackageName()); Log.d("", "cls:"+cn.getClassName()); Log.d("", "MainActivity.class.getName():"+MainActivity.class.getName()); if(!(cn.getClassName()).endsWith(MainActivity.class.getName())){ Intent i = new Intent(); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setClass(mService, MainActivity.class); mService.startActivity(i); } float brightness = (Float) msg.obj; SystemManager.setBrightness(MainActivity.mActivity,brightness); SystemManager.saveBrightness(MainActivity.mActivity.getContentResolver(),brightness); break; } } };}
d.ServerThread.java 主要负责服务端接收客户端的信息,并分配相应操作(未完成)
/** * 负责处理每个线程通信的线程类 * */public class ServerThread implements Runnable { //定义当前线程所处理的Socket Socket s = null; //该线程所处理的Socket所对应的输入流 BufferedReader br = null; /** * 连接请求 */ public static final String OPERATION_CONNECT = "CN"; /** * 方向 */ public static final String OPERATION_DIRECTION_TOP = "DT"; public static final String OPERATION_DIRECTION_BOTTOM = "DB"; public static final String OPERATION_DIRECTION_LEFT = "DL"; public static final String OPERATION_DIRECTION_RIGHT = "DR"; /** * 音量 */ public static final String OPERATION_VOLUME = "VO"; /** * 亮度 */ public static final String OPERATION_BRIGHTNESS = "BR"; /** * 单击&确认 */ public static final String OPERATION_CLICK = "CL"; /** * 菜单 */ public static final String OPERATION_MENU = "ME"; /** * 返回 */ public static final String OPERATION_BACK = "BA"; /** * 电源 */ public static final String OPERATION_POWER = "PO"; /** * Home */ public static final String OPERATION_HOME = "HO"; public ServerThread(Socket s) throws IOException { this.s = s; //初始化该Socket对应的输入流 br = new BufferedReader(new InputStreamReader(s.getInputStream() , "utf-8")); //② } /** * 向所有接入客户端发信息 sendMessageToClient */ public synchronized static void sendMessageToClient(String msg){ Log.v("", "sendMessageToClient --> : " + msg); //遍历socketList中的每个Socket, //将读到的内容向每个Socket发送一次 for (Socket s : SocketService.socketList) { try { OutputStream os = s.getOutputStream(); os.write((msg + "\n").getBytes("utf-8")); } catch (IOException e) { e.printStackTrace(); Log.e("", " --> sendMessageToClient error <-- 删除该Socket : " + s); //删除该Socket。 SocketService.socketList.remove(s); //① } } } /** * 此线程不断接收客户端发过来的请求信息 */ public void run() { try { String content = null; //采用循环不断从Socket中读取客户端发送过来的数据 while ((content = readFromClient()) != null) { if(content.length() < 2) continue; // 避免发送空指令 或错误指令导致异常 String requestStr = content.substring(0, 2); Log.v("", "SocketServer ---> content : " + content + " , requestStr : " + requestStr ); if(requestStr.endsWith(OPERATION_VOLUME)){ MainActivity.systemManager.CtrlVolume(content); }else if(requestStr.endsWith(OPERATION_BRIGHTNESS)){ MainActivity.systemManager.CtrlBrightness(content); } // TODO 未完成,下篇完成 } } catch (Exception e) { e.printStackTrace(); } } /** * 定义读取客户端数据的方法 * @return 接收到的请求信息 */ private String readFromClient() { try { return br.readLine(); } //如果捕捉到异常,表明该Socket对应的客户端已经关闭 catch (IOException e) { //删除该Socket。 SocketService.socketList.remove(s); MainActivity.mHandler.sendEmptyMessage(MainActivity.CONN_ERROR); } return null; }}
e.SystemManager.java 操作功能实现类,控制系统音量、系统亮度等(未完成)
/** * 控制系统的逻辑类 * @author zzp * */public class SystemManager { private AudioManager audioManager; private Activity mActivity; private static int brightnessNum = 2; public SystemManager(){ mActivity = MainActivity.mActivity; audioManager = (AudioManager)mActivity.getSystemService(Context.AUDIO_SERVICE); getScreenBrightness(mActivity); brightnessNum = (int)((getScreenBrightness(mActivity)*1.0/255) * 5) ; Log.v("", "---> getScreenBrightness(mActivity) : " + getScreenBrightness(mActivity)); Log.v("", "---> brightnessNum : " + brightnessNum); } /** * 媒体音量增减 */ public void CtrlVolume(String ctrlCode){ if(ctrlCode.contains("-")){ //第一个参数:声音类型 //第二个参数:调整音量的方向 //第三个参数:可选的标志位 audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI); }else{ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI); //调高声音 } } /** * 系统亮度增减 */ public void CtrlBrightness(String ctrlCode){ if(brightnessNum < 0 || brightnessNum > 5) brightnessNum = 2; if(ctrlCode.contains("-")){ if(brightnessNum > 0) brightnessNum--; }else{ if(brightnessNum < 5) brightnessNum++; } Message msg = new Message(); msg.what = SocketService.SET_BRIGHTNESS; msg.obj = (brightnessNum)*1.0f/5; SocketService.mHandler.sendMessage(msg); } /** * 获取屏幕的亮度 * * @param activity * @return */ public static int getScreenBrightness(Activity activity) { int nowBrightnessValue = 0; ContentResolver resolver = activity.getContentResolver(); try { nowBrightnessValue = android.provider.Settings.System.getInt( resolver, Settings.System.SCREEN_BRIGHTNESS); } catch (Exception e) { e.printStackTrace(); } return nowBrightnessValue; } /** * 设置系统亮度 * @param brightness : 0~1 */ public static void setBrightness(Activity activity, float brightness) { WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); lp.screenBrightness = brightness; //这里设置范围 0~1.0 activity.getWindow().setAttributes(lp); Settings.System.putInt(activity.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, (int)(brightness*255)); } /** * 保存亮度设置状态 * * @param resolver * @param brightness : 0~1 */ public static void saveBrightness(ContentResolver resolver, float brightness) { Uri uri = android.provider.Settings.System .getUriFor("screen_brightness"); android.provider.Settings.System.putInt(resolver, "screen_brightness", (int)(brightness*255)); resolver.notifyChange(uri, null); } }
f.WifiAdmin.java 与wifi相关类,可查询本机Wifi连接状态、IP地址、IP地址的int形式与IP显示形式转换
/** * 与wifi相关 */public class WifiAdmin { //定义WifiManager对象 private WifiManager mWifiManager; //定义WifiInfo对象 private WifiInfo mWifiInfo; //扫描出的网络连接列表 private List<ScanResult> mWifiList; //网络连接列表 private List<WifiConfiguration> mWifiConfiguration; //定义一个WifiLock WifiLock mWifiLock; //构造器 public WifiAdmin(Context context) { //取得WifiManager对象 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); //取得WifiInfo对象 mWifiInfo = mWifiManager.getConnectionInfo(); } //打开WIFI public void OpenWifi() { if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(true); } } //关闭WIFI public void CloseWifi() { if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(false); } } //锁定WifiLock public void AcquireWifiLock() { mWifiLock.acquire(); } //解锁WifiLock public void ReleaseWifiLock() { //判断时候锁定 if (mWifiLock.isHeld()) { mWifiLock.acquire(); } } //创建一个WifiLock public void CreatWifiLock() { mWifiLock = mWifiManager.createWifiLock("Test"); } //得到配置好的网络 public List<WifiConfiguration> GetConfiguration() { return mWifiConfiguration; } //指定配置好的网络进行连接 public void ConnectConfiguration(int index) { //索引大于配置好的网络索引返回 if(index > mWifiConfiguration.size()) { return; } //连接配置好的指定ID的网络 mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId, true); } public void StartScan() { mWifiManager.startScan(); //得到扫描结果 mWifiList = mWifiManager.getScanResults(); //得到配置好的网络连接 mWifiConfiguration = mWifiManager.getConfiguredNetworks(); } //得到网络列表 public List<ScanResult> GetWifiList() { return mWifiList; } //查看扫描结果 public StringBuilder LookUpScan() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < mWifiList.size(); i++) { stringBuilder.append("Index_"+new Integer(i + 1).toString() + ":"); //将ScanResult信息转换成一个字符串包 //其中把包括:BSSID、SSID、capabilities、frequency、level stringBuilder.append((mWifiList.get(i)).toString()); stringBuilder.append("/n"); } return stringBuilder; } //得到MAC地址 public String GetMacAddress() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getMacAddress(); } //得到接入点的BSSID public String GetBSSID() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getBSSID(); } //得到IP地址 public int GetIPAddress() { return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress(); } //得到连接的ID public int GetNetworkId() { return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId(); } //得到WifiInfo的所有信息包 public String GetWifiInfo() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.toString(); } //添加一个网络并连接 public void AddNetwork(WifiConfiguration wcg) { int wcgID = mWifiManager.addNetwork(wcg); mWifiManager.enableNetwork(wcgID, true); } //断开指定ID的网络 public void DisconnectWifi(int netId) { mWifiManager.disableNetwork(netId); mWifiManager.disconnect(); } //将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理 public static long ipToLong(String strIp){ long[] ip = new long[4]; //先找到IP地址字符串中.的位置 int position1 = strIp.indexOf("."); int position2 = strIp.indexOf(".", position1 + 1); int position3 = strIp.indexOf(".", position2 + 1); //将每个.之间的字符串转换成整型 ip[0] = Long.parseLong(strIp.substring(0, position1)); ip[1] = Long.parseLong(strIp.substring(position1+1, position2)); ip[2] = Long.parseLong(strIp.substring(position2+1, position3)); ip[3] = Long.parseLong(strIp.substring(position3+1)); return (ip[3] << 24) + (ip[2] << 16) + (ip[1] << 8 ) + ip[0]; } //将十进制整数形式转换成127.0.0.1形式的ip地址 public static String longToIP(long longIp){ StringBuffer sb = new StringBuffer(""); //将高24位置0 sb.append(String.valueOf((longIp & 0x000000FF))); sb.append("."); //将高16位置0,然后右移8位 sb.append(String.valueOf((longIp & 0x0000FFFF) >>> 8 )); sb.append("."); //将高8位置0,然后右移16位 sb.append(String.valueOf((longIp & 0x00FFFFFF) >>> 16)); sb.append("."); //直接右移24位 sb.append(String.valueOf((longIp >>> 24))); return sb.toString(); }}
g.Manifest.xml(配置文件)需要添加权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.GET_TASKS" />
四、后记&下篇内容
本例中设置系统亮度未能很好的实现,每次设置亮度都会进行判断当前系统显示的Activty,而设置亮度目前需要到服务端界面设置才成功,所以退出服务端界面后,设置系统亮度会进行判断,如果当前不是显示服务端MainActivity,则在服务中重新打开。
下篇主要内容是模拟系统按键消息,单击事件,滚动事件等。
参考引用:
1.《李刚疯狂Android讲义》十三章
2.Android APIs:http://developer.android.com/reference/java/net/ServerSocket.html