天天看點

Android開發之APN網絡切換

本人最近做了一個在android中進行ANP網絡切換,找到一篇寫的很不錯很詳細的文章,在此感謝部落客,原創連結:http://www.cnblogs.com/hanyonglu/archive/2012/03/29/2423298.html。

android4.0及以上系統版本,google已經把android.permission.WRITE_APN_SETTINGS權限收回,要用系統級簽名才能進行apn的修改與切換,否則運作時會報No permission to write APN settings ,Neither user xxx   nor current process has android.permission.WRITE_APN_SETTINGS.",即使手機root也不行。4.0以下版本則可無視!

以下為部落格原文:

下面先來看下本示例實作的效果圖:

Android開發之APN網絡切換
Android開發之APN網絡切換

 在我們點選左圖中"設定APN選項"時出現右邊的圖示效果,可以選我們項目用到的APN選項。

Android開發之APN網絡切換
Android開發之APN網絡切換

當我們點選"編輯APN内容"時出現右邊的圖示效果,我們可以對APN的内容進行編輯,這是在我們的"河南移動專網"APN選項已經存在時顯示如右邊的圖示效果。如果"河南移動專網"APN選項不存在,那麼第一次點選"編輯APN内容"時會出現如左邊下方顯示的Toast提示,需要再次點選"編輯APN内容"才可以進行編輯。

  下面來看下關于APN的基礎知識:

  APN(Access Point Name),即“接入點名稱”,用來辨別GPRS的業務種類,目前分為兩大類:CMWAP(通過GPRS通路WAP業務)、CMNET(除了WAP以外的服務目前都用CMNET,比如連接配接網際網路等)。

  APN的英文全稱是Access Point Name,中文全稱叫接入點,是您在通過手機上網時必須配置的一個參數,它決定了您的手機通過哪種接入方式來通路網絡。

  移動手機的預設上網配置有兩種:CMWAP和CMNET。一些使用移動辦公的大客戶,通常會使用專用APN,其接入點随意定義,隻要和該省營運商其他APN不沖突即可。

  CMWAP也叫移動夢網,通過該接入點可接入一個比較大的移動私網,網内有大量的手機應用下載下傳及資源通路。因為CMWAP不接入網際網路,隻接入移動營運商的私網,是以流量費用比較低廉。

  CMNET也叫GPRS連接配接網際網路,通常每個省的營運商會提供若幹個Internet出口以供CMNET撥号使用者使用。其流量費用較CMWAP要高一些。

  目前國内銷售的手機,如果是非智能機,通常已配置好CMWAP連接配接,智能機通常會配置CMWAP和CMNET連接配接。如需手動添加這些配置,請參考手機說明書。

  專有APN在功能上可以和Internet的VPN做類比,實際上他就是基于GPRS的VPN網絡。

  專有APN常見組網方式

  1,營運商部署一條專線接入到企業的網絡中,局端和企業端路由器之間采用私有IP進行連接配接。

  2,局端互連路由器與GGSN采用GRE隧道連接配接。

  專有APN的幾個重要特點:

  1,除非營運商配置設定一個Internet IP位址,否則計算機沒有任何辦法通過Internet通路該APN中的主機。

  2,隻有手機卡号在APN中的白名單之列,該手機才可以接入該APN。

  3,企業客戶可以建立一套RADIUS和DHCP伺服器,GGSN向RADIUS伺服器提供使用者主叫号碼,采用主叫号碼和使用者賬号相結合的認證方式;使用者通過認證後由DHCP伺服器配置設定企業内部的靜态IP位址。補充:該認證方式不一定适合于每個省的營運商,這取決于該省營運商的APN管理平台。

  GPRS專網系統終端上網登入伺服器平台的流程為:

  1)使用者發出GPRS登入請求,請求中包括由營運商為GPRS專網系統專門配置設定的專網APN;

  2)根據請求中的APN,SGSN向DNS伺服器發出查詢請求,找到與企業伺服器平台連接配接的GGSN,并将使用者請求通過GTP隧道封裝送給GGSN;

  3)GGSN将使用者認證資訊(包括手機号碼、使用者賬号、密碼等)通過專線送至Radius進行認證;

  4)Radius認證伺服器看到手機号等認證資訊,确認是合法使用者發來的請求,向DHCP伺服器請求配置設定使用者位址;

  5)Radius認證通過後,由Radius向GGSN發送攜帶使用者位址的确認資訊;

  6)使用者得到了IP位址,就可以攜帶資料包,對GPRS專網系統資訊查詢和業務處理平台進行通路。

  以上是關于APN的一些基礎知識。接下來,我們開始着手實作本示例的代碼,先來看下示例程式結構圖,如下所示:

Android開發之APN網絡切換

MainActivity.java檔案中主要是顯示APN設定,并以圓角ListView圓角呈現,實作顯示核心代碼如下:

private CornerListView cornerListView = null;
    private ArrayList<HashMap<String, String>> mapList = null;
    private SimpleAdapter simpleAdapter = null;
    private ApnUtility apnutility = null;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        // 設定視窗特征
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.setting_apn);
        
        apnutility = new ApnUtility(this);
        
        simpleAdapter = new SimpleAdapter(
                this, 
                getDataSource(),
                R.layout.simple_list_item_1, 
                new String[] { "item_title","item_value" }, 
                new int[] { R.id.item_title});
        
        cornerListView = (CornerListView) findViewById(R.id.apn_list);
        cornerListView.setAdapter(simpleAdapter);
        cornerListView.setOnItemClickListener(new OnItemListSelectedListener());
    }
    
    // 設定清單資料
    public ArrayList<HashMap<String, String>> getDataSource() {
        mapList = new ArrayList<HashMap<String, String>>();
        HashMap<String, String> map1 = new HashMap<String, String>();
        map1.put("item_title", "設定APN選項");
        HashMap<String, String> map2 = new HashMap<String, String>();
        map2.put("item_title", "編輯APN内容");
        mapList.add(map1);
        mapList.add(map2);
        
        return mapList;
    }
           

在這個ListView中一共有兩項:設定APN選項和編輯APN内容,為這兩項設定事件:

// ListView事件監聽器
    class OnItemListSelectedListener implements OnItemClickListener{
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position,long id) {
            // TODO Auto-generated method stub
            switch(position){
            case 0:
                openApnActivity();
                break;
            case 1:
                editMobileApn();
                break;
            }
        }
    }
           

設定APN選項主要是顯示本機的所有的APN清單:

// 設定APN選項
    private void openApnActivity(){
        Intent intent = new Intent(Settings.ACTION_APN_SETTINGS);
        startActivity(intent);
    }
           

編輯APN内容主要是編輯目前使用的APN内容,如果是本機設定了該項APN,則直接進入編輯界面;如果本機尚未設定該項APN,那麼在第一次點選時會提示資訊,第二次點選時才能夠編輯:

// 編輯APN内容
    private void editMobileApn(){
        int id = -1;
        Uri uri = Uri.parse("content://telephony/carriers");
        ContentResolver resolver = getContentResolver();
        Cursor c = resolver.query(uri, new String[] { "_id", "name",
                "apn" }, "apn like '%hnydz.ha%'", null, null);

        // 該項APN存在
        if (c != null && c.moveToNext()) {
            id = c.getShort(c.getColumnIndex("_id"));
            String name = c.getString(c.getColumnIndex("name"));
            String apn = c.getString(c.getColumnIndex("apn"));

            Log.v("APN", id + name + apn);

            Uri uri1 = Uri.parse("content://telephony/carriers/" + id);

            Intent intent = new Intent(Intent.ACTION_EDIT, uri1);
            startActivity(intent);
            apnutility.setDefaultApn(id);
        }else{
            // 如果不存在該項APN則進行添加
            apnutility.setDefaultApn(apnutility.AddYidongApn());
            Toast.makeText(getApplicationContext(), 
                    "再次點選APN内容即可編輯!", Toast.LENGTH_LONG)
                    .show();
        }
    }
           

 在編輯APN内容時,需要用ContentResolver查詢查詢Uri"content://telephony/carriers":

Cursor c = resolver.query(uri, new String[] { "_id", "name", "apn" }, "apn like '%hnydz.ha%'", null, null);
           

這裡是查詢apn的關鍵字,當然大家也可以查詢name的關鍵字,不過最好是查詢apn的關鍵字,因為name是随意命名的。

  ApnUtility.java檔案中是封裝關于Apn操作的常用方法一個類,下面看下其核心代碼實作。

  下面是将一個新的APN進行添加:

/**
     * 利用ContentProvider将添加的APN資料添加進入資料庫
     * @return
     */
    public int AddYidongApn() {
        int apnId = -1;
        GetNumeric();
        ContentResolver resolver = context.getContentResolver();
        ContentValues values = new ContentValues();

        values.put("name", EM_APN[0]);
        values.put("apn", EM_APN[1]);
        values.put("type", EM_APN[4]);
        values.put("numeric", NUMERIC);
        values.put("mcc", NUMERIC.substring(0, 3));
        Log.i("mcc", NUMERIC.substring(0, 3));
        values.put("mnc", NUMERIC.substring(3, NUMERIC.length()));
        Log.i("mnc", NUMERIC.substring(3, NUMERIC.length()));
        values.put("proxy", "");
        values.put("port", "");
        values.put("mmsproxy", "");
        values.put("mmsport", "");
        values.put("user", "");
        values.put("server", "");
        values.put("password", "");
        values.put("mmsc", "");

        Cursor c = null;
        
        try {
            Uri newRow = resolver.insert(APN_LIST_URI, values);
            if (newRow != null) {
                c = resolver.query(newRow, null, null, null, null);
                int idindex = c.getColumnIndex("_id");
                c.moveToFirst();
                apnId = c.getShort(idindex);
                Log.d("Robert", "New ID: " + apnId
                        + ": Inserting new APN succeeded!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        if (c != null)
            c.close();

        return apnId;

    }
           

該方法會傳回一個apnId,代表新添加的APN的Id,用以辨別該APN。

  NUMERIC是MCC和MNC的組合,一般是46002或46000。

  要根據apnId将設定的APN選中,如下代碼:

/**
     * 根據apnId将設定的APN選中
     * @param apnId
     * @return
     */
    public boolean setDefaultApn(int apnId) {
        boolean res = false;
        ContentResolver resolver = context.getContentResolver();
        ContentValues values = new ContentValues();
        values.put("apn_id", apnId);

        try {
            resolver.update(PREFERRED_APN_URI, values, null, null);
            Cursor c = resolver.query(PREFERRED_APN_URI, new String[] { "name",
                    "apn" }, "_id=" + apnId, null, null);
            if (c != null) {
                res = true;
                c.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return res;
    }
           

在設定前要判斷要設定的APN是否存在,因為如果存在就不用添加,如果不存在,則需要添加:

/**
     * 判斷要設定的APN是否存在
     * @param apnNode
     * @return
     */
    public int IsYidongApnExisted(ApnNode apnNode) {
        int apnId = -1;
        Cursor mCursor = context.getContentResolver().query(APN_LIST_URI, null,
                "apn like '%hnydz.ha%'", null, null);
        
        while (mCursor != null && mCursor.moveToNext()) {
            apnId = mCursor.getShort(mCursor.getColumnIndex("_id"));
            String name = mCursor.getString(mCursor.getColumnIndex("name"));
            String apn = mCursor.getString(mCursor.getColumnIndex("apn"));
            String proxy = mCursor.getString(mCursor.getColumnIndex("proxy"));
            String type = mCursor.getString(mCursor.getColumnIndex("type"));
            
            if (apnNode.getName().equals(name)
                    && (apnNode.getApn().equals(apn))
                    && (apnNode.getName().equals(name))
                    && (apnNode.getType().equals(type))) {
                return apnId;
            } else {
                apnId = -1;
            }
        }
        
        return apnId;
    }
           

 一般在程式中,我們最好設定成自動切換APN,隻需要調用如下方法即可:

/**
     * 轉換APN狀态
     * 将CMNET切換為要設定的APN
     */
    public void SwitchApn() {    
        // 判斷網絡類型
        switch (GetCurrentNetType()) {
        case NET_3G:
            // 如果3G網絡則切換APN網絡類型
            if (!IsCurrentYidongApn()) {
                EM_APN_ID = IsYidongApnExisted(YIDONG_APN);
                
                if (EM_APN_ID == -1) {
                    setDefaultApn(AddYidongApn());
                } else {
                    setDefaultApn(EM_APN_ID);
                }
            }
            break;
        case NET_WIFI:
            // 如果是無線網絡則轉換為3G網絡
            closeWifiNetwork();
            break;
        case NET_OTHER:
            // 如果是其他網絡則轉化為3G網絡
            break;
        default:
            break;
        }
    }
           

上例代碼中GetCurrentNetType()是判斷目前網絡的類型:

/**
     * 擷取目前網絡類型
     * @return
     */
    public int GetCurrentNetType() {
        int net_type = getNetWorkType();
        
        if (net_type == ConnectivityManager.TYPE_MOBILE) {
            return NET_3G;
        } else if (net_type == ConnectivityManager.TYPE_WIFI) {
            return NET_WIFI;
        }
        
        return NET_OTHER;
    }
           

 closeWifiNetwork()是關閉Wifi網絡,如果Wifi是打開着的話。

  IsCurrentYidongApn()是要設定的APN是否與目前使用APN一緻:

/**
     * 要設定的APN是否與目前使用APN一緻
     * @return
     */
    public boolean IsCurrentYidongApn() {
        // 初始化移動APN選項資訊
        InitYidongApn();
        YIDONG_OLD_APN = getDefaultAPN();
        
        if ((YIDONG_APN.getName().equals(YIDONG_OLD_APN.getName()))
                && (YIDONG_APN.getApn().equals(YIDONG_OLD_APN.getApn()))
                && (YIDONG_APN.getType().equals(YIDONG_OLD_APN.getType()))) {
            return true;
        }
        
        return false;
    }
           

InitYidongApn()是初始化要設定的APN資訊參數:

/**
     * 初始化移動APN資訊參數
     */
    protected void InitYidongApn() {
        YIDONG_APN = new ApnNode();
        YIDONG_APN.setName(EM_APN[0]);
        YIDONG_APN.setApn(EM_APN[1]);
        YIDONG_APN.setType(EM_APN[4]);
    }
           

getDefaultAPN()是擷取目前使用的APN資訊:

/**
     * 擷取目前使用的APN資訊
     * @return
     */
    public ApnNode getDefaultAPN() {
        String id = "";
        String apn = "";
        String name = "";
        String type = "";
        ApnNode apnNode = new ApnNode();
        Cursor mCursor = context.getContentResolver().query(PREFERRED_APN_URI,
                null, null, null, null);
        
        if (mCursor == null) {
            return null;
        }
        
        while (mCursor != null && mCursor.moveToNext()) {
            id = mCursor.getString(mCursor.getColumnIndex("_id"));
            name = mCursor.getString(mCursor.getColumnIndex("name"));
            apn = mCursor.getString(mCursor.getColumnIndex("apn"))
                    .toLowerCase();
            type = mCursor.getString(mCursor.getColumnIndex("type"))
                    .toLowerCase();
        }
        
        try {
            OLD_APN_ID = Integer.valueOf(id);
        } catch (Exception e) {
            // TODO: handle exception
            Toast.makeText(context, "請配置好APN清單!", Toast.LENGTH_LONG).show();
        }
        
        apnNode.setName(name);
        apnNode.setApn(apn);
        apnNode.setType(type);
        
        return apnNode;
    }
           

在我們結束程式時或是退出時需要将APN設定成預設的CMNET,否則影響正常上網功能:

/**
     * 關閉APN,并設定成CMNET
     */
    public void StopYidongApn() {
        if (IsCurrentYidongApn()) {
            // 初始化CMNET 
            InitCMApn();
            int i = IsCMApnExisted(CHINAMOBILE_APN);
            
            if (i != -1) {
                setDefaultApn(i);
            }
        }
    }
           

InitCMApn()是初始化CMNET參數資訊:

/**
     * 初始化預設的CMNET參數
     */
    protected void InitCMApn() {
        GetNumeric();
        
        CHINAMOBILE_APN = new ApnNode();
        CHINAMOBILE_APN.setName(CM_APN[0]);
        CHINAMOBILE_APN.setApn(CM_APN[1]);
        CHINAMOBILE_APN.setType(CM_APN[4]);
        CHINAMOBILE_APN.setMcc(NUMERIC.substring(0, 3));
        CHINAMOBILE_APN.setMnc(NUMERIC.substring(3, NUMERIC.length()));
    }
           

IsCMApnExisted()是判斷CMNET是否存在并傳回CMNET的apnId:

/**
     * 判斷CMNET是否存在
     * @param apnNode
     * @return
     */
    public int IsCMApnExisted(ApnNode apnNode) {
        int apnId = -1;
        Cursor mCursor = context.getContentResolver().query(APN_LIST_URI, null,
                "apn like '%cmnet%' or apn like '%CMNET%'", null, null);
        
        // 如果不存在CMNET,則添加。
        if(mCursor == null){
            addCmnetApn();
        }
        
        while (mCursor != null && mCursor.moveToNext()) {
            apnId = mCursor.getShort(mCursor.getColumnIndex("_id"));
            String name = mCursor.getString(mCursor.getColumnIndex("name"));
            String apn = mCursor.getString(mCursor.getColumnIndex("apn"));
            String proxy = mCursor.getString(mCursor.getColumnIndex("proxy"));
            String type = mCursor.getString(mCursor.getColumnIndex("type"));
            
            if ((apnNode.getApn().equals(apn)) && (apnNode.getType().indexOf(type) != -1)) {
                return apnId;
            } else {
                apnId = -1;
            }
        }
        
        return apnId;
    }
           

一般情況下,CMNET是預設存在的,如果CMNET不存在,則可以将CMNET添加進去,具體添加代碼可參照上面的示例,不再詳述。

在設定APN過程時,需要在配置檔案中設定權限,如下代碼:

<!-- 開關APN的權限 --> 
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
           

對于Android APN接入點相關的開發,有一個不錯的開源項目APNDroid的源代碼本地下載下傳,裡面包含了一個不錯的Widget架構,大家可以通過APNDroid源碼學習到有關接入點的相關問題,可以解決GPRS,尤其是國内的CMNET、CMWAP的切換和管理。工程API Level為3,可以運作在Android 1.5或更高的版本上。

繼續閱讀