本人最近做了一個在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以下版本則可無視!
以下為部落格原文:
下面先來看下本示例實作的效果圖:

在我們點選左圖中"設定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添加進去,具體添加代碼可參照上面的示例,不再詳述。
在設定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或更高的版本上。