Android网络篇
http://blog.csdn.net/u011014707/article/details/46584347
针对互联网设计的操作系统,网络编程、多媒体编程。基础框架构成了Android平台应用开发的三块柱石。本章围绕网络编程协议、网络编程接口、Web服务、XML解析、SIP、NFC、RIL等方面的知识。
另外,在Android 4.0中,开始支持流量的监控,对企业应用也增强了支持,通过VpnService允许应用构建自己的VPN。
1.无线接入技术概述
无线接入技术通常都隐藏在TCP/IP协议和平台框架层下。由硬件平台厂商开发完成,在一些封闭的移动系统中,甚至不向应用开发者提供可阅读的源代码。
3GPP标准的状态信息浏览地址:http://www.3gpp.org/specs/specs.htm.。
3GPP文档的下载地址:ftp://ftp.3gpp.rog/Spece/archive.
GSM&&GPRS文档的下载地址:ftp://ftp.3gpp.org/Specs/archive.
(1)GSM/EDGE
GPRS是GSM规范中实现的内容,目的是提供115kbps的分组数据业务,在实际环境中,GPRS的速率是14.4~43.2kbps,以分组交换来补充电路交换在数据业务上的不足。
EDGE是GPRS的进一步演进,主要是在GSM系统中采用了一种新的调制方法,即最先进的多时隙操作和8PSK调制技术,提供更为丰富的数据业务。
(2)TD-SCDMA
TD-SCDMA标准主要由中国提出,由于商用方面的严重延迟,更多的是扮演过渡角色,目前,TD-SCDMA在商业上已开始向TD-LTE演进。
(3)WCDMA
WCDMA主要由欧洲和日本提出。
(4)CDMA2000
CDMA2000标准主要由美国提出。
(5)LTE
LTE包括FDD-LTE和TDD-LTE两种类型。
(6)WiFi
作为最主要的局域网技术之一。
(7)WiMAX
作为Intel大力研发的广域网技术。
(8)BT
作为最常用的个域网技术。在Android中,采用的蓝牙协议栈是Bluez,作为一个开源的蓝牙协议栈,Bluez提供了BT所有的常用功能。
启动BT的方法如下:
if(!mAdapter.isEnabled()){
Intent intent =new Intent(BluetoothAdapter.ACTION_ERQUEST_ENABLE);
startActivity(intent);
}
设置BT可发现状态的方法如下:
Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 500);
startActivity(intent);
2.基础协议封装
Android对网络编程提供了3种接口,即Java接口、Apache接口、Android接口,这些接口都离不开基础的网络协议。Android提供了对HTTP、SSL、Cookie、DHCP等协议的封装,并支持套接字编程,同时对URI也提供了支持,除此之外,针对移动终端的特别需要,Android还提供了链接管理器和WiFi管理器来增强对网络的支持。
(1)HTTP协议
在Android中,HTTP协议的实现主要体现在android.net.http和org.apache.http等包中。在android.net.http中,主要通过AndroidHttpClient来实现HTTP协议,AndroidHttpClient实际上是Apach的DefaultHttpClient的子类。AndroidHttpClient能够处理Cookie,但是在默认情况下无法维护Cookie。设置Cookie的方法如下:
context.setAttribute(ClientContext.COOKIE_STORE, cookiestore);
不能直接创建AndroidHttpClient,但是通过其newInstance()方法可以获得AndroidHttpClient实例。AndroidHttpClient通常和HttpHost、HttpUriRequest、HttpContext、ResponseHandler一起发起HTTP请求及处理服务器响应。
org.apache.http包在网络通信中已经使用的非常广泛。
(2)SSL协议
SSL和TSL均是由曾经的互联网巨头Netscape设计的,都是针对Web的网络安全协议。这两个协议的当前版本分别为SSL 3.0和TSL 1.0。常见的HTTPS连接就采用了SSL技术。SSL协议的实现与数字签名技术密切相关。
在android.net.http包中,提供了SslCertificate和SslError来描述X509数字证书信息。在WebView中,通过getCerificate方法可以查看当前页面是否有用SSL证书,另外通过SslCertificate的saveState方法可以将证书信息保存在Bundle中,这有助于实现跨域的访问。
在javax.net,ssl包中,通过HttpsURLConnection可以管理HTTP连接,通过SSKServer-Socket可以创建SSL套接字。下面是创建SSL套接字的一个示例:
SSLSocketFactory factory=HttpsURLConnetion,getDefaultSSLSocketFactory();
try{
SSLContext context=SSLContext.getInstance("TLS");
TrustManager[] tm=new TrustManager[]{new FullX509TrustManager()};
context.init(null, tm, new SecureRandom());
factory=context.getSocketFactory();
}
Socket mSocket=factory.createSocket();
org.apache.http.conn.ssl包中还提供了SSLSocketFactory等类来执行SSL套接字的创建等。
(3)Cookie实现
Cookie是用于识别用户信息、进行Session跟踪而存储在用户本地终端的数据(通常经过加密)。顺便提一下,Cookie也是由Netscape发明的。
由于Cookie拥有自己的生命周期,可以存储用户信息,因此可能暴露用户隐私,使用Cookie具有一定风险。
在Android中,Cookie的管理主要位于WebView、java.net.、org.apache.http.cookie等包中。获得Cookie的方法如下:
DefaultHttpClient httoclient=new DefaultHttpClient();
HttpGet httpget = new HttpGet(http://www.163.com);
HttpResponse response=httpclient.execute(httpget);
HttpEntity entity=response.getEntity();
List<Cookie> cookie=httpclient.getCookieStore().getCookies();
通过Cookie的getDomain、getPath、getValue、getName、getPorts、getComment、getCommentURL等方法可以获取Cookie的信息,另外,对于基于模拟器的开发,可以通过网络分析工具直接查看HTTP数据包的情况来分析Cookie。
在WebView中,CookieManager可以用来设置、清除和获取Cookie。清空Cookie的方法如下:
CookieSyncManager。createInstance(getApplicationContext());
CookieManager.getInstance().removeAllCookie();
在Android的默认浏览器中,Cookie的信息保存在data\data\com.android.browser\databases\目录下的webview.db中。
(4)连接性管理
在Android中,ConnectivityManager提供对网络如WIFI、BT、WIMAX、GPRS、UMTS的连接性管理。获得ConnectivityManager服务的方法如下:
ConnectivityManager cnManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
通过ConectivityManager.getActivityNetworkInfo()可以获得接入方式,判断网络类型和当前状态等,示例如下:
NetworkInfo info=cnManager.getActiveNetworkInfo();
boolean isMobile=(info!=null && info.getType()==ConnectivityManager.TYPE_MOBILE); //是否是蜂窝网络
boolean isConected=info.isConnented(); //是否已连接
boolean isRoaming=isMobile && TelephonyManager.getDefualt().isNetworkRoaming(); //是否漫游
连接性管理要求应用的权限为android.permission.ACCESS_NETWORK_STATE。当网络发生变化时,系统会广播Action为android.net.conn.CONNECTIVITY_CHANGE的Intent消息。下面是处理连接变化的示例:
mNetworkStateIntentReceiver = new BroadcastReceiver(){
public void onReceive(Context context, Intent intent){
if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_AVTION)){
NetworkInfo info=intent.getParcelableExtra(ConectivityManager.EXTRA_NETWORK_INFO);
String typeName=info.getTypeName();
String subtypeName=info.getSubtypeName();
sendNetworkType(typeName.toLowerCase(), (subtypeName!=null ?subtypeName.toLowerCase() : ""));
onNetworkToggle(info.isAvailable());
}
}
};
(5)WiFi管理器
作为最常用的无线局域网络,WiFi几乎成为当前移动终端的标配。获取WifiManager服务的方法如下:
WifiManager wifiManager=(WifiManager)getSystemService(WIFI_SERVICE);
通过WifiManager可以获得设备可用网络的列表、获得当前激活网络的信息、执行热点烧苗、测定信号强度等。启动热点扫描的方法startScan()。
WiFI的信息,如BSSID、RSSI、SSID、网络ID、MAC地址等,由WifiInfo描述。通过WifiManager可以获得WifiInfo对象,方法如下:
WifiInfo wifiinfo=wifiManager.gerConnectionInfo();
支持WiFi点对点的通信(不经过网络和热点),获得WifiP2pManager服务的方法如下:
WifiP2pManager wifiP2pManager=(WifiP2pManager)getSystemService(WIFI_P2P_SERVICE);
为了进行点对点通信,需要经过如下几个步骤:通过initialize()初始化P2P连接。 通过discoverPeers()发现附近的设备。 通过connect启动P2P连接。
3.Java网络编程接口
Java网络通信有两种实现方式,一种是基于套接字的方式,另一种是基于HTTP的方式。本节只要介绍基于HTTP的实现,另外对底层NIO技术做简单的介绍。
(1)HTTP网络通道
在基于HTTP的实现中,最重要的一个类为HttpURLConnection,通过HttpURLConnection可以发送和接收数据。另外,通过HttpURLConnection还可以设置请求参数,如内容类型、回话Cookie等。下面是通过HttpURLConnection实现网络通信的简单示例:
HttpURLConnection urlConn=new URL("http://www.android.com").openConnection(); //打开HTTP连接
urlConn.setDoOutput(true); //设置请求参数时必须将其设置为true
urlConn.setRequestMethod("POST");
urlConn.setUseCache(false);
urlConn.setRequestProperty("Content-type". "application/x-www-form-urlencoded");
DataOutputStream dos=new DataOutputStream(urlConn.getOutputStream());
dos.writeBytes("name="+URLEncoder.encode("chenmouren","gb2312");
dos.flush();
dos.close();
BufferReader reader=new BufferedReader(new InputStreamReader(urlConn, getInputStream()));
reader.readLine();
reader.close();
urlConn.disconnect(); //释放所有HTTP连接资源
在将setDoOutput方法设置为true时,必须将HttpURLConnection设置为POST请求,而HttpURLConnection默认采用的时GET请求。所谓GET的请求是指请求参数附在URL后面直接传输,所谓POST请求表示请求参数位于HTTP的头部,POST请求的隐私性显然好些。
对于HTTPS协议,可通过HttpsURLConnection建立HTTPS连接。系统会先尝试TSL和SSL2,当链接建立失败时,再尝试SSL 3。
对HTTP请求返回错误相应时,会在HttpURLConnection的getInputStream方法中抛出IOException,通过getErrorStream可以获得出错相应。另外通过getHeaderFields方法可以读取HTTP头部。
部分网络链接可能会导致重定向。下面是判断是否重定向的方法:
HttpURLConnection urlConnection=(HttpURLConnection)url.openConnection();
try{
InputStream in=new BufferedInputStream(urlConnection.getInputStream());
if(!url.getHost().equals(urlConnection.getURL().getHost())){
//已经重定向了
}finally{
urlConnection.disconnect();
}
}
另外,Cookie的管理是通过CookieManager和CookieHandler进行的。
(2)NIO简介
按照UNIX网络编程划分,I/O模型可以分为阻塞I/O、非阻塞I/O、I/O复用、信号驱动I/O、异步I/O。按照POSIX标准来划分,I/O模型值分为两类:同步I/O、异步I/O。
在Java中,目前有3中I/O方式可供选择,即BIO(Blockding I/O)NIO(New I/O)、AIO(Asynchronous I/O)。其中AIO于J2SE 7中引入,有时也称为NIO 2。NIO和AIO均为非阻塞I/O。
在J2SE1.4中,Sun引入了NIO,在进行Java应用向Android移植时,会出现一些Java NIO的兼容性问题,通常是NIO对IPv6的支持存在问题,会抛出java.net.SocketException:Bad address family异常。目前解决的办法是,通过setProperty(propObj, put, "java,net.preferIPv6Addresses", "false")来禁止IPv6,然后进行Selector.open()等操作。
注意,在Linux中,Java NIO是基于管道实现的。
NIO主要使用了Channel和Selector来实现,其基于事件驱动的单线程处理可以有效地节省系统开销,并降低线程死锁的概率。
NIO作为一种中高负载的I/O模型,相对于传统BIO并发的效率大大提高。在Android中,其涉及的包有java.nio、java.nio.channels、java.nio.channels.spi、java.nio.channels.spi、java.nio.charset、java.nio.charset.spi。
在实际的开发中,NIO通常用于本地数据复制和网络传输中。
4.Apache网络编程接口
在Apache中,主要通过HttpClient执行HTTP通信,通过HttpResponse获得服务器响应。执行GET请求的示例如下:
String httpUrl=http://www.android.com;
HttpGet httpRequest =new HttpGet(httpUrl). //GET请求
try{
HttpClient httpclient=new DefaultHttpClient();
HttpResponse httpResponse = httpclient.execute(httpRequest);
if(httpResponse.getStatusLine().getStatueCode==HttpStatus.SC_OK){
...
}
}
为了在POST请求中传递参数,需要将参数封装到NameValuePair中。POST请求的示例如下:
String url =http://www.google.com;
HttpPost request=new HttpPost(url); //POST请求
List<NameValuePair> params=new ArralList<NameValuePair>();
params.add(new BasicNameValuePair("name", "zhangsan"));
HttpEntity entity=new UrlEncodeForEntity(params, "gb2312");
request.setEntity(entity);
HttpClient client=new DefaultHttpClient();
HttpResponse response=client.execute(request);
if(response.getStatusLine().getStatusCode()==HttpStatus.SC_OK{
...
}
5.Android网络编程接口
Android的网络编程接口是对Apache的再封装和简化。目前Android支持RTP、SIP、HTTP、WiFi、SSL、代理等网络协议。
(1)HTTP通信
Android的HTTP通信和Apche的不同点主要体现在HttpClient的实现上。Android的HTTP通信主要是通过AndroidHttpClient而非DefaultHttpClient和Apache的HttpGet、HttpPost、HttpResponse等来是现代额。通过AndroidHttpClient可以创建一个HttpClient;通过HttpGet可以获取URI等信息,自动处理服务器的重定向;通过HttpResponse可以处理服务器的相应。通过AndroidHttpClient来实现HTTP通信的示例如下:
try{
AndroidHttpClient client=AndroidHttpClient.newInstance(userAgent());
HttpGet httpGet=new HttpGet(http://www.163.com/); //GET请求
HttpResponse response=client.execute(httpGet);
if(response.getStatusLine().getStatusCode()!=HttpStatue.SC_OK){
//正常相应
}
client.Close();
}
Android的POST请求的实现请参考Apache的POST请求实现。
(2)RTP协议
实时传输协议主要用于VOIP、push-to-talk、电话会议等场景中。
android.net.rtp包主要包括AudioCodec、AudioGroup、AudioStream、RtpStream等类,其中AudioCodec用于定义语音编码集;AudioGroup是扬声器、麦克风和AudioStream的集合,它对CPU、宽带的要求较高,当有多个AudioGroup对象运行时,只有具有MODE_ON_HOLD状态的AudioGroup才能运行。AudioGroup要求应用具有android.permission.RECORD_AUDIO权限;RtpStream和AudioStream要求应用具有android.permission.INTERNET权限。发起SIP语音通信的示例如下:
mAudioStream=new AudioStream(InetAddress.getByName(getLocalIp())); //构建语音流
public void startAudio(){
try{
startAudioInternal();
}
}
private synchronized void startAudioInternal() throws UnknownHostException{
if(mpeerSd==null){
throw new IllegalStateException("mPeerSd=null");
}
stopCall(DONT_RELEASE_SOCKET);
mInCall=true;
SimpleSessionDescription offer=new SimpleSeesionDescription(mPeerSd);
AudioStream stream=mAudioStream;
AudioCodec codec=null;
for(Media media : offer.getMedia()){
if((codec==null)&&(media.getPort() > 0) && "audio".equals(media.getType()) && "RTP/AVP".equals(media.getProtocol())){
for(int type : media.getRtpPayloadType()){
codec=AudioCodec.getCodec(type, media.getRtpmap(type), media.getFmtp(type); //获得编码
if(codec!=null){
break;
}
}
if(codec!=null){
String address=media.getAddress();
if(address==null){
address=offer.getAddress();
}
stream.associate(InetAddress.getByName(address), media.getPort()); //绑定远程端点
stream.getDtmfType(-1); // 设置DTMF
stream.setCodec(codec); //设置编码
for(int type : media.getRtpPayloadTypes()){
String rtpmap=media.getRtpmap(type);
if((type!=codec.type)&&(rtpmap!=null) &&(rtpmap.startWith("telephone-event")){
stream.setDtmfType(type);
}
}
if(mHold){
stream.setMode(RtpStream.MODE_NORMAL); //设置模式
}else if(media.getAttribute("recvonly")!=null){
stream,setMode(RtpStream.NODE_SEND)ONLY);
}else if(media.getAttribute("sendonly")!=null){
stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
}else if(offer.getAttribute("recvonly")!=null){
stream.setMode(RtpMode(RtpStream.MODE_SEND_ONLY);
}else if(offer.getAttribute("sendonly")!=null){
stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
}else{
stream.setMode(RtpStream.MODE_NORMAL);
}
break;
}
}
}
if(codec==null){
throw new IllegalStateException("Reject SDP: no suitable codecs");
}
if(isWifiOn()){
grabWifiHighPerfLock();
}
if(!Hold){
((AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE)).setMode(AudioManager.MODE_NORMAL);
}
AudioGroup audioGroup = getAudioGroup();
if(mHold){
}else{
if(audioGroup==null)
audioGroup=new AudioGroup();
stream.join(audioGroup);
}
setAudioGroupMode();
}
private void setAudioGroupMode(){
AuidoGroup audioGroup=getAudioGroup();
if(audioGroup!=null){
if(mHold){
audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
}else if(mMuted){
audioGroup.setMode(AudioGroup.MODE_MUTED);
}else if(isSpeakerOn()){
audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
}else{
audioGroup.setMode(AudioGroup.MODE_NORMAL);
}
}
}
6.Web服务实现
在移动互联网快熟发展的今天,为用户提供有价值的服务成为行业创新的重要特征,开发具有联网能力的客户端成为从业者的一个基本功能。在客户端和服务器端的通信实现方面,目前主要的两种方法,即HTTP协议和分布式计算。HTTP协议无须多言,在分布式计算中,目前最主要的技术是Web服务。
(1)Web服务概述
Web服务时基于XML和HTTPS的一种分布式计算服务,其通信协议主要基于SOAP,服务通过WSDL描述,发现和获得服务元数据则通过UDDI来描述。
调用Web服务的方式包括RPC、SOA、REST等。Web服务的安全可以通过Web-Security、SSL、数字证书等3中方式来实现,通过Web-Reliability可进行可信赖的Web服务间消息传递。
目前Web服务的框架有Ajax2、metro、cxf等。
(2)KSOAP2的实现
KSOAP2是一个针对Android平台的轻量级的SOAP库,适用于资源受限的环境,不支持Web-Security。
KSOAP2比较重要的类包括AndroidHttpTransport、AndroidServiceConnection、SoapEnvelope、SoapObject等。SoapEnvelope目前支持3个版本,即1.0、1.1、1.2。
KSOAP2的下载地址为http://code.google.com/p/ksoap2-android/,最新的版本为2.5.7。
为了实现通过KSOAP2的Web服务调用,通常需要经过如下几个步骤:
(a)准备参数。
(b)配置SoapObject。
(c)SoapEnvelope调用。
下面详细介绍Web服务调用过程。
1)为了实现Web服务,需要配置URL、名字空间、方法名等,示例如下:
public static String NAMESPACE=http://core.mfsp.com;
public static String URL=http://ws.demo.com/axis2/services/mfsp;
public static String METHOD_NAME="usersService";
2)配置SoapObject
传送给服务器的信息要作为属性封装在SoapObject中,SoapObject的本质为一个KvmSerializable序列化对象。创建SoapObject的示例如下:
SoapObject soapObject=new SoapObject(NAMESPACE, METHOD_NAME);
设置SoapObject属性的方法如下:
public SoapObject addProperty(String name, Object value);
3)SoapEnvelope调用
单有SoapObject是不够的,还要把SoapObject封装到SoapEnvelope才能发送给服务器,示例如下:
String xmlStr =null;
HttpTransportSE ht=new HttpTransportSE(URL);
SoapSerializationEnvelope envolope=new SoapSerializationEnvelope(SoapEnvelope.VER11); //设置版本
envelope.dotNet =false;
envelope.setOutputSoapObject(soapObject);
ht.call(null, envelope); //发送给服务器
xmlStr = envelope.getResponse().toString(); //获得返回值
7.XML解析
在Android中,系统提供了3中XML解析器,即Pull解析器、DOM解析器、SAX解析器,开发者可以根据使用的场景进行选择。Pull解析器用于处理简单的XML数据,DOM解析器和SAX解析器均可解析复杂的数据,其中DOM解析器是完全加载后才能解析,而SAX解析器可以随时加载随时解析。
在具体的解析器选择上,DOM解析器适合处理文件不大(比如10KB以内的文件),嵌套的分支比较多,需要反复查询的场景;相比DOM解析器将XML文件整个加入到RAM中,SAX更适合用于网络中数据无法一次完成获取的场景,其好处是占用的内存少;Pull解析器适用于进行一些简单的XML加载,如Android选项菜单的配置文件等。假设XML字符串xmlStr的内容如下:
<response>
<result>...</result>
<body>...</body>
</response>
(1)Pull解析器
Pull解析器是最简单的XML解析器,其主要通过依次分析XML标签来进行解析,解析过程主要通过XmlPullParserFactory构建的XmlPullParser进行的,下面是Pull解析器的一个示例:
try{
XmlPullParserFactory factory=XmlPullParseFactory.newInstance();
XmlPullParser parser=factory.newPullParser(); //创建Pull解析器
parser.setInput(new ByteArrayInputStream(xmlStr.getBytes()), "UTF-8"); //设置输入流
int type =parser.getEventType();
if(type==XmlPullParser.START_COCUMENT){
do{
type=parser.next();
if(type==XmlPullParser.START_TAG){
String name=parser.getName();
if(name.equals("result")){
...
}else if(name.equals("body")){
...
}
while(type!=XmlPullParser.END_DOCUMENT);
}
}
}
(2)DOM解析器
DOM解析的实现非常简单,首先通过DocumentBuilderFactory构建DocumentBuilder对象,然后利用DocumentBuilder对象即可开始解析工作,对结点的处理主要通过结点遍历来实现。DOM解析XML字符串的示例如下:
DocumentBuilderFactory dbfactory=DocumentBuilderFactory.newInstance();
try{
DocumentBuilder db=dbfactory.newDocumentBuilder(); //创建DocumentBuilder
InputStream in=ByteArrayInputStream(xmlStr.getBytes());
InputSource is=new InputSource(in);
Document dom=db.parse(is);
org.w3c.dom.Element docEle=dom.getDocumentElement();
NodeList ni=docEle.getElementsByTagName("response"); //根结点
for(int i=0; i < n1.getLength(); i++){
Node item=n1.item(i);
NodeList properties=item.getChildNodes();
for(int j=0; j < properties.getLength(); j++){ //遍历子结点
Node property=properties.item(j);
String name=property.getNodeName();
if(name.equals("result")){ //第一个子节点
result=property.getFirstChild().getNodeValue();
}else if(name.equals("body")){ //第二个子节点
if(property.getFirstChild()!=null){
}
}
}
}
in.close();
}
在Honeycomb中,getElementsByTagName()方法无法获得NodeList,即DOM解析器在Honeycomb中会因严重的Bug而无法使用。
(3)SAX解析器
SAX的解析更加简单,通过SAXParserFactory构建SAXParser解析器,然后通过XMLReader即实现XML的解析,示例如下:
RootElement root =new RootElement("response");
Element result=root.getChild("result");
result.setEndTextElementListener(new EndTextElementListener(){
public void end(String body){
...
}
});
Element body =root.getChild("body");
body.setEndTextElementListener(new EndTextElementListener(){
public void end(String body){
...
}
});
SAXParserFactory factory=SAXParserFactory.newInstance();
try{
SAXParser parser=factory.newSAXParser();
XMLReader xmlreader=parser.getXMLReader();
xmlreader.setContextHandler(root.getContentHandler()); //设置内容句柄
InputSource is=new InputSource(new StringReader(xmlStr());
try{
xmlreader.parse(is); //解析输入流
}
}
8.套接字编程
通过套接字可以实现基于TCP/UDP协议的通信,关于套接字的原理,下面介绍Android的套接字用法。
(1)基于TCP协议的套接字
客户端发起通信前,必须知道服务器端的IP地址或域名,Java通过InetAddress来表示IP地址,Java同时支持IPv4和IPv6两种协议。
1)服务器端
服务器端的套接字通过ServerSocket实现,通过ServerSocket可以直接绑定端口,监听该端口传来的消息。
通过ServerSocket的accept方法可为传入的连接请求创建Socket实例,并将已成功连接的Socket实例返回给服务器套接字。如果没有连接请求,accept方法将阻塞等待。
通过Socket的getInputStream方法可获得输入流,查看传入的消息;通过Socket的getOutputStream方法可以相应客户端,实例如下:
ServerSocket aServerSocket=null;
Socket aSessionSocket=null;
DataInputStream aDataInput=null;
DataOutputStream aDataOutput=null;
try{
aServerSocket=new ServerSocket(8000); //监听8888端口
}catch(Exception e){
e.printStackTrace();
}
while(true){
try{
aSessionSocket=aServerSocket.accept(); //有请求接入
aDataInput=new DataInputStream(aSessionSocket.getInputStream());
String msg=aDataInput.readUTF(); //读取消息
aDataOutput=new DataOutputStream(aSessionSocket.getOutputStream()); //相应请求
aDataOutput.write("ok"); //返回OK
}catch(Exception e){
e.printStackTrace();
}
aDataInput.close();
aDataOutput.close();
aSessionSocket.close();
}
2)客户端
在客户端,可以设置代理、服务器域名、服务器IP地址、端口等信息。下面是客户端套接字实现示例:
Socket s=null;
DataOutputStream dout=null;
DataInputStream din=null;
try{
s=new Socket(“your server ip", 8888); //服务器的IP地址、端口
dout=new DataOutputStream(s.getOutputStream());
din=new DataInputStream(s.getInputStream());
dout.writeUTF("reg"); //发送请求
String msg=din.readUTF(); //读取相应消息
}catch(Exception e){
e.printStackTrace();
}
dout.close();
din.close();
s.close();
(2)基于UDP协议的套接字
基于UDP协议的套接字主要是通过DatagramSocket进行的,DatagramSocket在通信的两端均适用。通过DatagramSocket可以绑定IP地址和端口等。
DatagramSocket对象通过send方法发送消息,通过receive方法接收消息,通过connect方法建立和服务器的连接。
1)服务器端
服务器端主要用来接收数据,方法如下:
try{
DatagramSocket ds=new DatagramSocket(6000);
byte[] buf=new byte[100];
DatagramPacket dp=new DatagramPacket(buf, 100);
ds.receive(dp);
ds.close();
}catch(Exception ex){
ex.ptintStackTrace();
}
(2)客户端
客户端主要用来发送数据,示例如下:
try{
DatagramSocket ds=new DatagramSocket();
String str="this is lisi";
DatagramPacket dp=new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("localhost"), 6000);
ds.dend(dp);
ds.close();
}catch(Exception ex){
ex.printStackTrace();
}
9.Web应用实现
在Android中,除了浏览器外,Google还提供了WebView控件来支持基于网页和本地应用的混合实现,这混合实现和基于Web服务的本地应用有着本质的不同,其在开发上更侧重于网页脚本语言和Java语言的混合。WebView的实现:
WebView和Android浏览器一样均是基于Webkit渲染引擎的,在默认情况下,Webkit渲染引擎的部分功能被关闭,宽泛地讲,WebView是Android浏览器的子集。WebView要求应用具有android.permission.INTERNET权限。
另外还提供了WebDriver用于更方便地测试基于WebView的应用
(1)加载本地数据
WebView支持本地HTML数据的加载,但在加载时需要指明数据的MIME类型和采用的编码方式,方法如下:
final String mimetype="text/html";
final String encoding="utf-8";
wv=(WebView)findViewById(R.id.wv1);
String data="<a href='x'>Hello Word! - 1</a>";
wv.loadData(data, mimeType, encoding);
(2)自定义界面呈现
通过设置WebChromeClient客户端,WebView可以影响网页的加载过程,如加载进度、JS对话框、上传文件、文字选中等,示例如下:
wv.setWebChromeClient(new WebChromeClient(){
...
});
要监听页面加载的进度,可以在onProgressChanged(WebView view, int newProgress)方法中进行,另外,在WebView中无法弹出JS对话框,需要开发者自行在本地应用中进行处理,下面是监听进度变化的方法:
wv.setWebChromeClient(new WevChromeClient(){
public void onProgressChanges(WebView view, int progress){
setProgress(progress*1000); //调用Activity的setProgress方法
}
});
(3)自定义渲染过程
通过设置WebViewClient客户端,WebView可以影响网页内容的渲染过程,示例如下:
wv.setWebViewClient(new WebViewClient(){
...
});
要在页面加载前进行自定义的处理,可以在onLoadResource(WebView view, String url)方法中进行;要在页面开始加载时进行自定义的处理,可以在onPageStarted(WebView view, String url, Bitmap favicon)方法中进行;要在页面加载结束时进行自定义处理,可以在onPageFinished(WebView view,String url)方法中进行,要覆盖默认的按键处理过程,可以在shouldOverrideKeyEvent(WebView view, KeyEvent event)方法中进行,下面是一个示例:
wv.setWebViewClient(new WebViewClient(){
public void onPageStarted(WebView view, Sting url, Bitmap favicon){
if(url.equals(....)){
showDialog(DIALOG_WAIT);
}
}
public void onPageFinished(WebView view, String url){
if(url.equals(...)){
dismissDialog(DIALOG_WAIT);
}
}
public boolean shouldOverrideUrlLoading(WebView view, String url){
view.loadUrl(url);
return true;
}
});
另外,WebViewClient还支持对网页错误、鉴权等方面的处理。
(4)WebView设置
WebView的设置是通过WebSettings来实现的,方法如下:
public WebSettings getSettings()
通过WebSettings可以设置或获取WebView的多种设置,如cache、数据库、字体及字体大小、编码、界面缩放、JS、插件、渲染速度等。下面仅介绍常用的几种WebView引擎设置:
wv.getSettings().sePluginState(PluginState.ON_DEMAND) //插件
wv.getSettings().setJavaScriptEnable(true) //JSP
wv.getSettings().setSavePassword(true) //密码
wv.getSettings().setCacheMode(WebSettings.LOAD_NORMAL); //cache模式
WebView的cache模式包括LOAD_DEFAULT、LOAD_NORMAL、LOAD_CACHE_ELSE_NETWORK、LOAD_NO_CACHE、LOAD_CACHE_ONLY。
通过WebSettings还可以设置与UI相关的配置,常见的几种配置如下:
wv.getSettings().setBuiltInZoomControls(true) //缩放
wv.getSettings().setLightTouchEnable(true) //触控事件
wv.getSettings().setDefaultZoon(WebSettings.ZoomDensity.NEDIUM) //缩放密度
其中和本地应用一样,WebView支持多种密度,其中ZoomDensity值包括FAR、DEDIUM、CLOSE,当然也可以自定义密度值。
(5)与JSP交互
考虑到越来越多的网页以JSP实现,Android还对Java和JSP之间的交互提供支持。为了支持JSP加载,必须进行如下设置:
wv.getSettings().setJavaScriptEnable(true);
通过WebView可以将本地对象和JSP对象绑定,方法如下:
public void addJavascriptInterface(Object obj, String interfaceName)
上述代码中obj为java对象,interfaceName为JSP中的对象。需要注意,绑定Java实现需另外调动线程来运行。
(6)Cookie的管理
WebView同样支持Cookie的管理,但是要注意,在进行下载等业务时,有时需要WebView与AndroidHttpClient配合使用。
Cookie主要指为辨别用户身份,进行Session跟踪而储存在用户终端上的数据。
Cookie以<key,value>键值对的形式存在,Cookie在生成时会被指定一个期限值,即Cookie的生命周期,当Cookie的生命周期结束时,Cookie即被自动清除。通过抓包工具可以分析报文中Cookie的信息。
在Android中,Cookie的管理是通过CookieSyncManager来实现的,通常为了获得优异的性能,在RAM中保存Cookie,但是由于某种需要,通过CookieSyncManager可以将Cookie保存到永久存储空间中。
CookieSyncManager在运行期间以单子模式出现,一单创建了CookieSyncManager,系统将单独启动一个线程来执行Cookie在RAM和永久存储空间之间的同步,系统会通过一个定时器没5min执行一次同步操作。
创建CookieSyncManager实例的方法如下:
CookieSyncManager.createInstance(this);
执行Cookie在RAM和永久存储之间的同步的方法如下:
CookieSyncManager.getInstance().startSync();
CookieSyncManager.getInstance().stopSync();
上述代码中,startSync方法和stopSync方法均用于单次同步,通常在驻留应用的Activity的onResume方法中执行startSync方法,在Activity的onPause方法中执行stopSync方法。希望利用实时进行Cookie同步而非等待定时器时,可用如下方法实现:
CookieSyncManager.getInstance().sync();
需要注意,sync方法是异步执行的,并不受UI线程的影响,通常在WebViewClient中调用,调用方法如下:
public void onPageFinished(WebView view, String url);
对于上层应用而言,通过CookieSyncManager并不能直接管理Cookie,通常Cookie的管理是通过CookieManager来实现的。CookieManager在系统中也是以单子模式的方法运行的。创建CookieManager实例的方法如下:
CookieManager cookieManager=CookieManager.gerInstance();
CookieManager对Cookie的管理本质上是对CookieSyncManager的封装,因此在管理Cookie时,必须创建CookieSyncManager示例。获取或设置Cookie的方法如下:
cookieManager.setCookie(url, value) //设置Cookie
cookieManager.getCookie(url) //获取Cookie
判断是否有Cookie存在的方法如下:
public synchronized booleam hasCookies()
是否激活Cookie的方法如下:
public synchronized void setAcceptCookie(boolean accept)
根据场景的不同,有时需要移除Cookie,相应的CookieManager的方法如下:
public void removeSeddionCookie() //移除Session的Cookie
public void removeAllCookie() //移除所有的Cookie
public void removeExpiredCookie() //移除到期的Cookie
(7)目标环境的适配
考虑到Android设备的屏幕复杂多变,Google在WebView中同样支持密度的变化。WebView开始支持DOM、CSS和元标签等属性,以帮助开发者适配密度的变化。应用这些属性的方法如下:
对于DOM,相关的属性为window.devicePixelRatio,其值为1.0(MDPI)、1.5(HDPI)、0.75(LDPI)等。
对于CSS,相关的属性为-webkit-device-pixel-ratio,其值同DOM,下面是一个相关的示例:
<link rel=”stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" />
对于viewport的元标签,其相关的属性为target-densitydpi,其值为device-dpi(使用设备的原生DPI作为目标DPI)、high-dpi、medium-dpi、low-dpi等,下面是一个相关的示例:
<meta name="viewport" content="target-densitydpi=device-dpi" />
10.SIP服务
SIP(Session Initiation Protocol)服务在Android 2.3中正式引用,能够支持VOIP。由于SIP和传统的通话不同,其通过的是PS域业务,因此本节将SIP服务归为多媒体部分进行介绍。
不同于传统的H232,SIP的传输协议是基于IP协议的,在应用层基于XML,能够独立于底层传输协议TCP、UDP和SCIP(Stream Control Transmission Protocol),用于建立、修改和终止IP网上的双方和多方多媒体回话。SIP协议借鉴了HTTP、SMTP等协议,支持代理、重定向及登记定位用户等功能,支持用户移动。通过与RTP/RTCP、SDP、RTSP等协议及DNS配合,SIP支持语音、视频、数据、E-mail、状态、IM、聊天、游戏等。SIP协议支持TCP和UDP两种传输协议,由于SIP本身具有握手机制,因此首选UDP。
SIP系统实际上有两部分:客户端和服务器。
关于SIP协议栈的内容,可以参考RFC2543、RFC3261、RFC3262、RFC3263、RFC3264、RFC3265。
使用SIP,要求应用具有android.permission.INTERNET和android.permission.USE_SIP权限。考虑到即时软件平台采用了Android2.3,在硬件配置上也可能会不完全支持SIP,所以在AndroidManifest.xml中还应声明android.hardware.sip.voip、android.hardware.wifi、android.hardware.microphone等。
在Android中,SIP客户端的实现位于android.net.sip包中,主要的类包括SipManager、SipAudioCall、SipProfile、SipSession等。
(1)SipManager
SipManager用于初始化SIP连接和接入SIP服务等。通过SipManager能够创建SIP会话,比较常用的方法如下:
createSipSession() //创建SIP回话
getCallID() //获得呼叫ID
makeAudioCall() //发起语音呼叫
newInstance() //创建一个SipManager示例
register() //注册账号
takeAudioCall() //接听电话
open() //打开SIP回话
另外,支持SIP并不意味着一定支持VOIP,是否支持SIP协议栈和VOIP功能与硬件设备有关。在基于SIP发起语音呼叫前,还要判断是否支持SIP和语音呼叫,方法分别为isVoipSupported()和isApiSupported()。
(2)SipAudioCall
SipAudioCall用于处理基于SIP的网络语音呼叫,通过SipManager的makeAudioCall()方法和takeAudioCall方法客户获得SipAudioCall的实例。
需要注意的是,SipAudioCall要求应用具有android.permission.INTERNET和android.permission.USE_SIP权限。
另外SipAudioCall的startAudio方法要求应用具有android.permission.ACCESS_WIFI_STATE、android.permission.WAKE_LOCK、android.permission.RECORD_AUDIO权限。SipAudioCall的setSpeakerMode方法要求应具有android.permission.MODIFY_PHONE_STATE权限。
SipAudioCall的常用方法如下:
startAudio() //在一个已建立的SIP链接中发起通话
setSpeakerMode() //设置通话模式
toggleMute() //进行静音模式
getPeerProfile() //获取对方的信息
endCall() //结束一个语音呼叫SIP链接
answerCall() //应答一个语音呼叫SIP链接
makeCall() //发起一个语音呼叫SIP链接
sendDtmf() //发送DTMF
另外,通过设置SipAudioCall.Listener监听器可以监听到SIP链接建立和结束的消息。在SIP语音呼叫链接建立后,就可以传输RTP流了。在RFC3551中对SDP采用的RTP流进行明确的定义,其语音流为AMR格式。
(3)SipProfile
SipProfile包含了一个SIP账户的账户名、密码、端口、SIP域和SIP服务器地址等信息。
通过SipProfile.Builder可以构建一个SipProfile实例,也可以从SipSession中获取本地和对方的SipProfile实例SipProfile的常用方法如下:
getPassword() //获得密码
getUriString() //获得URI
getSipDomain() //获得SIP域
getDisplayName() //获得显示名
getUserName() //获得账户名
(4)SipSession
SipSession表示一个SIP会话,通过SipManager的createSipSession方法可以获得发起呼叫时的SIP会话,通过SipManager的getSessionFor方法可以获得收到呼叫时的SIP会话。
通过SipSession.Listener监听器可以监听到会话的信息,如呼叫忙、呼叫结束、呼叫建立、注册请求、铃声等。
通过SipSession.State监听器可以监听到会话的状态,SIP会话的状态目前有DEREGISTERING、INCOMING_CALL、INCOMING_CALL_ANSWERING、IN_CALL、NOT_DEFINED、OUTGOING_CALL、OUTGOING_CALL_ANCELING、OUTGOING_CALL_RING_BACK、PINGING、READ_TO_CALL、REGISTERING等。
SipSession的常用方法如下:
getPeerProfile() //获取对方的信息
setListener() //设置SipSession.Listener监听器
register() //注册到SIP服务器
在框架层,SIP协议栈的具体实现位于frameworks\base\voip中,和其他服务一样,在框架上采用的同样是C/S架构,其架构服务为SipService。
11.NFC通信
作为一种超短距无线接入技术,其实际通信距离小于4cm,工作频率为13.56MHz,传输速率为106~848bps,通常用于移动支持等场景中。
NFC的实现位于包android.nfc中。NFC支持的数据格式为Ndef。
在Android 4.0中,Google为NFC增加了Android Beam功能,支持从一个设备向另一个设备传送Ndef消息,Ndef消息封装在NdefMessage中。
要开发NFC相关的应用,需拥有权限android.permission.NFC,当然最重要的NFC的应用时移动终端支持NFC功能。设计在安装应用时判断硬件设备是否有NFC功能的方法如下:
<uses-feature android:name="android.hardware.nfc" android:required="true"/>
为了传送Ndef消息,Android提供了两种模式:一种是NfcAdapter的SetNdefPushMessage方法,根据用户的需要随时传送:一种是在Android Bean初始化时通过设置NfcAdapter.CreateNdefMessageCallback、传送。要了解Ndef消息是否传送完成,可以通过设置NfcAdapter.OnNdefPushCompleteCallback来监听,如果希望应用能够处理Ndef消息,那么需实现的Intent过滤器如下:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<data android:mimeType="mime/type“/>
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
<meta-date android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter.xml"/>
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
</intent-filter>
注意,在Android2.3中,仅支持基于android.nfc.action.TAG_DISCOVERED的过滤器。
12.RIL层处理
RIL层是对底层蜂窝接入技术的封装,是Android和协议栈通信的接口。它提供了语音、数据、短信、SIM、卡管理以及STK应用等功能,其实现的接口遵循无线协议规范。
在Android中,RIL层的代码主要位于hardware\ril中,包含6个子目录:include、libril、mock-ril、reference-cdma-sms、reference-ril、rild。
(1)RIL框架
从通信的角度讲,RIL框架上大致可以分为应用层。框架层、Linux用户控件层、Linux内核层、硬件层等,RIL层的内容主要分布在框架层和Linux用户空间层。从某种意义上讲,RIL层是通话服务(Telephony Service)和基带硬件中间的抽象层。RIL层在通信框架中的位置如下图所示:
1)框架层
框架层主要与RIL守护进程进行通信,其上层是与应用层通信的电话服务,其和RIL守护进程间的通信通过LocalSocket进行。
在RIL.java中,RIL类会维护一个RILRequest队列mRequestsList。通过RILSender发送RILRequest请求时,RIL会将请求加入mRequestList队列中,将待决请求计数加1;当RILReceiver中收到处理后的响应,会将待决请求计数减1。整个处理过程是以基于异步的方式进行的。RIL本质上是一个AT命令的实现。
向RIL守护进程发送RILRequest的过程如下:
private void send(RTLRequest rr){
Message msg;
if(mSocket==null){
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
}
msg=mSender.obtainMessage(EVENT_SEND, rr);
acquireWakeLock();
msg.sendToTarget();
}
接收到RIL守护进程返回的结果后进行如下处理:
private void processReponse(Paecel p){
int type;
type=p.readInt(); //数据传输方式为Parcel
if(type==RESPONSE_UNSOLICITED){
processUnsolicited(p);
}else if(type==RESPONSE_SOLICITED){
processSolicited(p);
}
releaseWakeLockIfDone();
}
从以上代码可以看出,相应类型分为RESPONSE_UNSOLICITED和RESPONSE_SOLICITED两种。其中RESPONSE_UNSOLICITED将会有BaseCommands中的助手对象直接处理,处理的内容主要是蜂窝模块主动上报的信号强度、基站信息等,共31种相应,这些相应在ril_unsol_commands.h中定义;RESPONSE_SOLICITED则通过RILReciver的异步通知机制传递命令的发送者并进行相应处理,如拨号等主动请求的动作。
RILRequest的请求共113种,这些请求在ril_commands.h中定义。另外,在SIMRecords.java中记录着系统支持的运营商信息,其MCC和MNC信息记录在MCCMNC_CODES_HAVING_3DIGITS_MNC数组中。
2)RIL守护进程
RIL守护进程和框架层进行的通信是通过套接字实现的。套接字通信主要是通过RIL_register中调用套接字助手函数android_get_control_socket来实现的,具体的实现如下:
s_fdListen=android_get_control_socket(SOCKET_NAME_RIL);
if(s_fdListen<0){
exit(-1);
}
ret=listen(s_fdListen, 4);
if(ret<0){
exit(-1);
}
RIL守护进程对套接字的事件处理如下:
ril_event_set(&s_listen_event, s_fsListen, false, listenCallback, null);
rilEventAddWakeup(&s_listen_event);
在Init.goldfish.sh等配置脚本中,厂商可以根据实际情况对RIL进行配置,这主要是通过设置ro.radio.noril环境变量来实现的。如果ro.radio.noril环境变量设置为yes,表示设备不支持蜂窝通信,自然RIL守护进程不会启动。
RIL守护进程在启动时会先查找相应的rild.lib路径和rild.libargs系统属性来确定要用的VENDOR RIL及要提供给VENDOR RIL的初始化参数;然后加载相应的VENDOR RIL,即librefrence_ril.so并启动线程打开时间循环和监听事件,接着初始化VENDOR RIL,这是直接与Modem通信的部分;最后调用RIL_register在协议栈上注册并打开接收上层命令的套接字通道。
在Android中,通过RIL_startEventLoop打开时间队列,其中事件队列的实现采用了I/O多路转换技术,即先构造一个有关I/O描述符的列表,然后调用select函数,当I/O描述符列表中的一个描述符准备好进行读或写时,该函数返回,并告知可以读或写哪个描述符。通过这种方法既避免了阻塞I/O的阻塞问题又避免了非阻塞I/O对CPU时钟的浪费问题。
RIL_Init()是移植过程中必须实现的函数。VENDOR RIL通过AT命令通道和包数据通道来与基带进行通信,这也是直接和硬件打交道的地方。
(2)TIL移植
在Android源代码中,给出了一个VENDOR RIL的参考实现,即reference-ril.c,并最终将其编译成librefrence.so共享库。reference-ril.c负责与硬件的通信,将来自上层的命令转换为AT指令,然后传递给硬件。根据硬件厂商的不同,OEM厂商需要修改reference-ril.c。下面是AT命令的一个实现:
static void onRadioPowerOn()
{
#ifdef USE_TI_COMMANDS
at_send_command("AT%CPHS=1", NULL);
at_send_command("AT%CTZV=1",NULL);
#endif
pollSIMState(NULL);
}
AT命令后的字母和数字表示具体的功能,其具体含义在3GPP协议栈中有定义。
VENDOR RIL总体上包括reference-ril.c、archannel.c、misc.c、at_tok.c等。
需要注意的是,如果定义了RIL_SHLIB,那么VENDOR RIL会被编译为共享库,或直接被编入RIL守护进程。
13.报文分析
在进行网络编程时,经常会需要判断发送的报文是否正确,发送的时间是否恰当。这些可通过报文分析实现。业界已有开源的抓包工具可用,Wireshark就是一个网络抓包分析工具。
Wiresshark可以用来分析ARP、TCP、UDP、IP、HTTP等数据包的信息。通过Capture菜单的Options选项可以设置捕获过滤器,如过滤协议、网卡等。在基于网络的应用开发时,要确认数据是否正确,即可使用Wireshark。