Android開發之APN網絡切換


  本文介紹Android平台中關於APN網絡切換的相關知識以及如何實現APN切換。

 

  由於最近的項目中用到APN切換的功能,所以就借着這個機會介紹一下APN的相關知識及如何在Android實現切換過程。關於APN的基本知識我會在下面給大家介紹。

  在這個示例中,我使用圓角ListView顯示效果,關於Android實現ListView圓角效果,大家可以查看我以前的一篇博文:http://www.cnblogs.com/hanyonglu/archive/2012/03/18/2404820.html

 

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

 

   

 

  在我們點擊左圖中"設置APN選項"時出現右邊的圖示效果,可以選我們項目用到的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的一些基礎知識。接下來,我們開始着手實現本示例的代碼,先來看下示例程序結構圖,如下所示:

 

  

 

  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添加進去,具體添加代碼可參照上面的示例,不再詳述。

 

  ApnNode是一個關於APN的實體類,具體可以查看示例代碼。

 

  CornerListView.java是關於圓角ListView的類,繼承自ListView,具體可以查看我的另一篇文章:Android實現ListView圓角效果--http://www.cnblogs.com/hanyonglu/archive/2012/03/18/2404820.html

  

  在設置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或更高的版本上。

  

  下載地址:點擊下載 

 

  以上就是關於Android中APN切換網絡的相關知識及使用過程。

 

  本示例下載地址:點擊下載

 

  最后,希望轉載的朋友能夠尊重作者的勞動成果,加上轉載地址:http://www.cnblogs.com/hanyonglu/archive/2012/03/29/2423298.html  謝謝。
 

  完畢。^_^ 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM