Android網絡篇

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章