由於最近的項目中用到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常見組網方式
3,企業客戶可以建立一套RADIUS和DHCP服務器,GGSN向RADIUS服務器提供用戶主叫號碼,采用主叫號碼和用戶賬號相結合的認證方式;用戶通過認證后由DHCP服務器分配企業內部的靜態IP地址。補充:該認證方式不一定適合於每個省的運營商,這取決於該省運營商的APN管理平台。
以上是關於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過程時,需要在配置文件中設置權限,如下代碼:
< 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 謝謝。
完畢。^_^