Android网络编程实践之旅
来源:互联网 发布:阿里云收费 编辑:程序博客网 时间:2024/05/16 14:20
(一):网络状态检测
一直以来本人都在做Android Multi-Media Framework下的Lib支持库的开发和修改,终于最近告一段落,但根据项目要求,需要写一个和网络相关的service,用java来实现。其实,在Framework及其之上的应用层用java开发,本人并不陌生,此前也做过一段时间,包括定制View,实现界面特效以及多媒体播放器和音乐编辑器,都做过。唯一遗憾的是,自进入嵌入式领域以来,从来没有做过网络相关的程序设计,此次,欣然答应,就是想借机来填补下个人职业生涯中的一项空白,呵呵,于私于公都是有利的。 开始进入正题。
网络状态检测的目的是检测当前设备是否已经连接到网络,属于何种类型,是否可用等信息,这些是进行正常网络通信的前提。这里需要说明,这里提供的sample程序,如无说明,默认都是在emulator上运行的,OS是2.3版本的。
首先,介绍网络状态检测的两个核心class:
1)、ConnectivityManager($SOURCE/frameworks/base/core/java/android/net/ConnectivityManager.java):给出网络连接状态,并在网络连接改变时(如由Wi-Fi连接变为Bluetooth连接)通知应用程序,主要职责有以下几个:
(1)、监视网络连接(Wi-Fi,GPRS,UMTS,BT等等);
(2)、在网络连接发生变化时,向应用发送broadcast Intent;
(3)、在网络连接失败时,尝试进行“失败转接”到其它可用网络
(4)、提供API,允许应用程序查询可用网络的粗粒度和细粒度状态
2)、NetworkInfo($SOURCE/frameworks/base/core/java/android/net/NetworkInfo.java):描述给定类型的网路接口的状态,截止到2.3.4(3.0以上的源码尚未开放),网络连接仅支持Mobile和Wi-Fi。顺便说一下,该class是个网络状态信息存储体,实现了Parcelable class中的部分接口,其余的接口都是进行网络状态的设置和查询。值得注意的是:NetworkInfo源码中提到两个概念:coarse-grained state(粗粒度状态)和fine-grained state(细粒度状态),这和1)的第四点职责相对应。通过分析源码,coarse-grained state包括:CONNECTING(正在连接), CONNECTED(已连接), SUSPENDED(挂起), DISCONNECTING(正在断开连接),DISCONNECTED(连接断开),UNKNOWN(未知状态)。对于fine-grained state这里也不做太多说明,根据源码注释来看,应用程序基本上用的都是coarse-grained state,极少使用fine-grained state。当然,分析源码我们可以看出,实际的网络连接是按fine-grained state进行状态迁移的,只是Android已经进行了fine-grained state到coarse-grained state的映射,是通过一个状态映射表来完成的,举例来说:如将fine-grained state的(IDLE+SCANNING+CONNECTING+AUTHENTICATING+OBTAINING_IPADDR)都映射为coarse-grained state的DISCONNECTED。
其次,我的sample程序:
(1)、main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:id="@+id/netinfo"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="network information"
- />
- </LinearLayout>
(2)、Activity所在.java文件NetworkExplorer.java
- package com.android.sample.NetworkExplorer;
- import android.app.Activity;
- import android.content.Context;
- import android.net.ConnectivityManager;
- import android.net.NetworkInfo;
- import android.os.Bundle;
- import android.widget.TextView;
- public class NetworkExplorer extends Activity {
- ConnectivityManager cgr;
- NetworkInfo netinfo;
- TextView netinfo_tv;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- netinfo_tv = (TextView)findViewById(R.id.netinfo);
- cgr = (ConnectivityManager)this.getSystemService(Context.CONNECTIVITY_SERVICE);
- }
- @Override
- protected void onStart() {
- super.onStart();
- netinfo = cgr.getActiveNetworkInfo();
- netinfo_tv.setText(netinfo.toString());
- }
- }
最后,我的捉虫之旅
启动emulator,并执行上面的代码,结果屏幕显示一个大大的Sorry,看着就来气。看看logcat给出下面一大段Error信息:
- ERROR/AndroidRuntime(1985): FATAL EXCEPTION: main
- ERROR/AndroidRuntime(1985): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.sample.
- NetworkExplorer/com.android.sample.NetworkExplorer.NetworkExplorer}: java.lang.SecurityException:
- ConnectivityService: Neither user 10042 nor current process has
- android.permission.ACCESS_NETWORK_STATE.
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1622)
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1638)
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:928)
- ERROR/AndroidRuntime(1985): at android.os.Handler.dispatchMessage(Handler.java:99)
- ERROR/AndroidRuntime(1985): at android.os.Looper.loop(Looper.java:123)
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread.main(ActivityThread.java:3647)
- ERROR/AndroidRuntime(1985): at java.lang.reflect.Method.invokeNative(Native Method)
- ERROR/AndroidRuntime(1985): at java.lang.reflect.Method.invoke(Method.java:507)
- ERROR/AndroidRuntime(1985): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
- ERROR/AndroidRuntime(1985): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
- ERROR/AndroidRuntime(1985): at dalvik.system.NativeStart.main(Native Method)
- ERROR/AndroidRuntime(1985): Caused by: java.lang.SecurityException: ConnectivityService:
- Neither user 10042 nor current process has android.permission.ACCESS_NETWORK_STATE.
- ERROR/AndroidRuntime(1985): at android.os.Parcel.readException(Parcel.java:1322)
- ERROR/AndroidRuntime(1985): at android.os.Parcel.readException(Parcel.java:1276)
- ERROR/AndroidRuntime(1985): at android.net.IConnectivityManager$Stub$Proxy.getActiveNetworkInfo(IConnectivityManager.
- java:345)
- ERROR/AndroidRuntime(1985): at android.net.ConnectivityManager.getActiveNetworkInfo(ConnectivityManager.java:242)
- ERROR/AndroidRuntime(1985): at com.android.sample.NetworkExplorer.NetworkExplorer.onStart(NetworkExplorer.java:29)
- ERROR/AndroidRuntime(1985): at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129)
- ERROR/AndroidRuntime(1985): at android.app.Activity.performStart(Activity.java:3791)
- ERROR/AndroidRuntime(1985): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1595)
- ERROR/AndroidRuntime(1985): ... 11 more
- ......(略)
- <application android:icon="@drawable/icon"
- ......(略)
- </application>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
分析emulator的网络连接如下:
1)、网络连接类型(type):主类型是mobile,子类型是UTMS(University Mobile Telecommunications System,3G);
2)、连接状态(state):CONNECTED/CONNECTED(已连接);
3)、采用该连接的原因(reason):simLoaded(已加载sim卡),目前本人尚未留意android上网络连接的选择原则,如:同时有Mobile和Wi-Fi,则选择何种连接方式;
4)、附加信息(extra):internet(可以使用IP network);
5)、是否漫游(roaming):false,不解释
6)、是否失败转接(failover):false,见前面ConnectivityManager职责说明中的第三点
7)、是否可用(isAvailable):true,不解释
(二)Socket通信机制
Socket(套接字)是一种通信机制,可以实现单机或跨网络进行通信,其创建需要明确的区分C(客户端)/S(服务器端),支持多个客户端连接到同一个服务器。有两种传输模式:1)、面向连接的传输:基于TCP协议,可靠性高,但效率低;
2)、面向无连接的传输:基于UDP协议,可靠性低,但效率高;
Android中,直接采用Socket通信应该是我们遇到的最低级的网络运用。尽管已经作了很大程度的抽象,但是纯粹的Socket通信,仍然给开发者留下很多细节需要处理,尤其在服务器端,开发者需要处理多线程以及数据缓冲等的设计问题。相对而言,处于更高抽象层的HTTP等,已经对Socket通信中需要处理的技术细节进行了很好的封装,开发者无须关心,因此,HTTP在网络开发中通常具有决定性的优势。
Android在其核心库的java包中,提供了用于客户端的Socket class和用于服务器端的ServerSocket class,分别位于$SOURCE/libcore/luni/src/main/java/java/net/Socket.java和$SOURCE/libcore/luni/src/main/java/java/net/ServerSocket.java文件中。分析两个class的源码,可以看出封装考虑的很全面,只构造方法一向每个class都考虑了很多种使用情况。由于本人只是初学者,很多理解的不深入,这里只抛砖引玉的对两个class的构造方法分别介绍一种,就是我下面的程序中用到的:
Socket(String dstName, int dstPort):创建一个以流的方式(基于TCP协议)连接到目标机(这里可以理解为服务器)的客户端Socket;dstName是目标机的IP地址,dstPort是要连接的目标机的端 口号。这里要注意对端口的理解,它不能理解为物理上的一个接口,而是对计算机中一块特殊内存区域的形象表述。
ServerSocket(int aport):创建一个绑定到本机指定端口的服务端Socket;aport就是指定的本机端口。与上述客户端Socket对应,通过TCP连接时,ServerSocket创建后需要在aport端口上进行监听,等待客户端的连接。
上面所写都是些背景知识,下面对本人的编程实践进行详细说明。
1、功能描述
1)、简单的基于Socket的数据通信;
2)、采用TCP方式连接;
3)、采用C/S结构,但服务端只支持一个连接;
4)、客户端能够向服务端发送数据,并显示服务端的返回信息;
5)、服务端能够接收客户端的数据,并将收到的数据以特定的方式返回给客户端;
2、程序实现思路
1)、服务端:设计为在后台执行的service,用一个独立的线程来处理客户端的连接请求、数据接收和返回。为了启动该service,编写个简单的Activity。
2)、客户端:设计为一个Activity,界面由三部分组成:显示服务端返回信息的文本区域(一个文本框);进行数据输入的编辑区域(一个编辑框);以及触发连接请求并执行数据发送的触发区域(一个按钮)。
3、服务端源程序
1)、Activity文件SocketServerDemo.java
- package com.android.sample.SocketServerDemo;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- public class SocketServerDemo extends Activity{
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // TODO Auto-generated method stub
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- System.out.println("begin start service");
- this.startService(new Intent(this, SocketService.class));
- }
- @Override
- protected void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- this.stopService(new Intent(this, SocketService.class));
- }
- }
- package com.android.sample.SocketServerDemo;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.io.PrintWriter;
- import java.net.ServerSocket;
- import java.net.Socket;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- public class SocketService extends Service{
- Thread mServiceThread;
- Socket client;
- @Override
- public IBinder onBind(Intent intent) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public void onCreate() {
- // TODO Auto-generated method stub
- super.onCreate();
- mServiceThread = new Thread(new SocketServerThread());
- }
- @Override
- public void onStart(Intent intent, int startId) {
- // TODO Auto-generated method stub
- super.onStart(intent, startId);
- mServiceThread.start();
- }
- @Override
- public void onDestroy() {
- // TODO Auto-generated method stub
- super.onDestroy();
- }
- public class SocketServerThread extends Thread {
- private static final int PORT = 54321;
- private SocketServerThread(){
- }
- @Override
- public void run() {
- try {
- ServerSocket server = new ServerSocket(PORT);
- while(true){
- System.out.println("begin client connected");
- client = server.accept();
- System.out.println("client connected");
- BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
- System.out.println("read from client:");
- String textLine = reader.readLine();
- if(textLine.equalsIgnoreCase("EXIT")){
- System.out.println("EXIT invoked, closing client");
- break;
- }
- System.out.println(textLine);
- PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())));
- writer.println("ECHO from server: " + textLine);
- writer.flush();
- writer.close();
- reader.close();
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- System.err.println(e);
- }
- }
- }
- }
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.sample.SocketServerDemo"
- android:versionCode="1"
- android:versionName="1.0">
- <uses-sdk android:minSdkVersion="9" />
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".ScreenCastServer"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service android:name="com.android.sample.SocketServerDemo.SocketService">
- </service>
- </application>
- <uses-permission android:name="android.permission.INTERNET"/>
- </manifest>
1)、布局文件main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:id="@+id/receive_msg"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- />
- <EditText
- android:id="@+id/edit_msg"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- />
- <Button
- android:id="@+id/send_msg"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="send"
- />
- </LinearLayout>
- package com.android.sample.SocketClientDemo;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.net.UnknownHostException;
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.TextView;
- public class SocketClientDemo extends Activity {
- private static final String SERVERIP = "192.168.1.68";
- private static final int SERVERPORT = 54321;
- TextView mMsgRev;
- EditText mMsgEdit;
- Button mMsgSendBtn;
- String mSendMsg;
- String mReceivedMsg;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mMsgRev = (TextView)findViewById(R.id.receive_msg);
- mMsgEdit = (EditText)findViewById(R.id.edit_msg);
- mMsgSendBtn = (Button)findViewById(R.id.send_msg);
- mMsgSendBtn.setOnClickListener(new OnClickListener(){
- @Override
- public void onClick(View v) {
- Socket socket = null;
- mSendMsg = mMsgEdit.getText().toString();
- try {
- socket = new Socket(SERVERIP, SERVERPORT);
- PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
- writer.println(mSendMsg);
- writer.flush();
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- mReceivedMsg = reader.readLine();
- if(mReceivedMsg != null){
- mMsgRev.setText(mReceivedMsg);
- }else{
- mMsgRev.setText("receive data error");
- }
- writer.close();
- reader.close();
- socket.close();
- } catch (UnknownHostException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- });
- }
- }
5、执行环境搭建
(三)网络状态检测
前面写过一篇关于网络状态检测的博文章,看连接点击打开链接。那片文章中,只是检测当前处于活动状态的网络。而且,还有一个不确定的问题:当设备中有多个可用的活动网络时,也只能显示其中之一。在本文中,给出枚举当前设备中所有网络及其状态的方法。
实现的方法很简单,修改连接文章中,sample程序中的类NetworkExplorer.java的代码如下:
- package com.android.sample.NetworkExplorer;
- import android.app.Activity;
- import android.content.Context;
- import android.net.ConnectivityManager;
- import android.net.NetworkInfo;
- import android.os.Bundle;
- import android.widget.TextView;
- public class NetworkExplorer extends Activity {
- ConnectivityManager cgr;
- NetworkInfo netinfo_arry[];
- TextView netinfo_tv;
- int i;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- netinfo_tv = (TextView)findViewById(R.id.netinfo);
- netinfo_tv.setEnabled(false);
- cgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- }
- @Override
- protected void onStart() {
- super.onStart();
- netinfo_arry = cgr.getAllNetworkInfo();
- for(i = 0; i < netinfo_arry.length; i++){
- netinfo_tv.append("Net " + (i + 1) + ": " + netinfo_arry[i].toString() + "\n\n");
- }
- }
- }
1、模拟器上:
2、开发板上:
- Android网络编程实践之旅
- Android 网络编程实践
- Android网络编程实践之旅(一):网络状态检测
- Android网络编程实践之旅(二):基于Socket的网络通信
- Android网络编程实践之旅(三):网络状态检测(续)
- Android网络编程实践之旅(二):基于Socket的网络通信
- Android网络编程实践之旅(二):基于Socket的网络通信
- Android网络编程实践之旅(二):基于Socket的网络通信
- Android网络编程实践之旅(二):基于Socket的网络通信 .
- Android网络编程实践之旅(四):使用WebView浏览网页
- Android网络编程实践之旅(五):利用系统浏览器打开网页
- android之网络编程
- Android之网络编程
- Android之网络编程
- Android网络请求框架之Retrofit实践
- android之socket网络编程
- Android网络编程之WebView
- Android网络编程之httpclient
- 未分类--Windows API--CallCPLEntry16
- 世界他妈就是个悖论
- 英特尔的C++编译器发布13.0版本
- 系统QSORT
- 获得整月的头一天和最后一天
- Android网络编程实践之旅
- Tiny6410挂载nfs根目录文件系统
- 系统SORT
- mac编译jni 调用java
- Oracle常见hint
- 循环渐进Nagios---操作篇(二)配置Nagios
- 未分类--Windows API--CanShareFolderW
- 系统QUEUE
- Eclipse背景颜色设置(设置成豆沙绿色保护眼睛,码农保护色)