針對互聯網設計的操作系統,網絡編程、多媒體編程。基礎框架構成了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。