本文為菜鳥窩作者蔣志碧的連載。“從 0 開始開發(fā)一款直播 APP ”系列來聊聊時(shí)下最火的直播 APP,如何完整的實(shí)現(xiàn)一個(gè)類"騰訊直播"的商業(yè)化項(xiàng)目
每個(gè)程序猿必備的110本經(jīng)典編程書,免費(fèi)領(lǐng)取地址:http://mp.weixin.qq.com/s/cx433vAj_CDLzmhOoUS6zA
一、前言
現(xiàn)在大多數(shù) APP 都需要從網(wǎng)絡(luò)獲取數(shù)據(jù),訪問網(wǎng)絡(luò)在所難免。但是訪問網(wǎng)絡(luò)之前,我們應(yīng)該做下網(wǎng)絡(luò)狀態(tài)判斷,而不是直接使用 HTTP 訪問網(wǎng)絡(luò)。很多人開發(fā)經(jīng)常忽略這塊內(nèi)容跳過網(wǎng)絡(luò)判斷,直接訪問網(wǎng)絡(luò),當(dāng)斷網(wǎng)時(shí),用戶不知,導(dǎo)致用戶體驗(yàn)度差,更甚者,當(dāng)訪問某個(gè)大流量數(shù)據(jù)時(shí),用戶不希望使用移動(dòng)網(wǎng)絡(luò),而應(yīng)該使用 Wi-Fi,特別是看高清電影,一部電影看下來流量估計(jì)報(bào)表,SIM 卡估計(jì)都直接負(fù)擔(dān)不起那昂貴的費(fèi)用了,這時(shí)候用戶會(huì)在心里罵上千萬遍。
如果我們的 APP 在加載圖片或者大數(shù)據(jù)下載操作時(shí),提醒用戶應(yīng)該切換到 Wi-Fi 網(wǎng)絡(luò)進(jìn)行操作,這樣就增加了用戶沾粘性,體驗(yàn)效果也會(huì)好很多,特別是直播類 APP。判斷網(wǎng)絡(luò)連接是否良好,連接Wi-Fi還是移動(dòng)網(wǎng)絡(luò),斷網(wǎng)或者網(wǎng)絡(luò)改變?cè)撊绾翁幚?,都需要注意其?xì)節(jié)。
二、登錄判斷網(wǎng)絡(luò)狀態(tài)
由于模擬器不支持移動(dòng)網(wǎng)絡(luò),筆者就用真機(jī)測(cè)試了,但是筆者住的地方?jīng)]有 Wi-Fi,因此若想要看 Wi-Fi 網(wǎng)絡(luò)判斷,只能通過模擬器了。真機(jī)錄屏看上去很模糊,但是也只能將就啦。
判斷移動(dòng)網(wǎng)絡(luò)狀態(tài)
判斷 Wi-Fi 網(wǎng)絡(luò)狀態(tài)

三、網(wǎng)絡(luò)狀態(tài)詳解
3.1、添加權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
3.2、定義常量來標(biāo)識(shí)幾種網(wǎng)絡(luò)類型
//Wi-Fi
public static final int NETTYPE_WIFI = 0;
//無網(wǎng)絡(luò)
public static final int NETTYPE_NONE = 1;
//2G
public static final int NETTYPE_2G = 2;
//3G
public static final int NETTYPE_3G = 3;
//4G
public static final int NETTYPE_4G = 4;
3.3、判斷網(wǎng)絡(luò)是否連接
我們?cè)谧鲈L問的時(shí)候都得進(jìn)行判斷是否連網(wǎng)。判斷連網(wǎng)也比較簡單,就用到了兩個(gè)類。ConnectivityManager 和 NetworkInfo。
/**
* 檢測(cè)網(wǎng)絡(luò)是否連接
* @return
*/
public static boolean checkNetWorkState(Context context){
boolean flag = false;
//得到網(wǎng)絡(luò)連接信息
manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//判斷網(wǎng)絡(luò)是否連接
if (manager.getActiveNetworkInfo() != null){
flag = manager.getActiveNetworkInfo().isAvailable();
}
if (flag){
//判斷網(wǎng)絡(luò)類型
isNetworkAvailable(context);
}else {
//若網(wǎng)絡(luò)未連接,則彈出提示進(jìn)行設(shè)置
setNetWork(context);
}
return flag;
}
3.4、判斷網(wǎng)絡(luò)類型
/**
* 網(wǎng)絡(luò)已經(jīng)連接,判斷是 Wi-Fi 還是移動(dòng)網(wǎng)絡(luò)(2G、3G、4G)
* @return
*/
public static void isNetworkAvailable(Context context) {
NetworkInfo.State gprs = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
NetworkInfo.State wifi = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
if (gprs == NetworkInfo.State.CONNECTED || gprs == NetworkInfo.State.CONNECTING){
Log.e("GPRS","移動(dòng)網(wǎng)絡(luò)已打開!");
}
if (wifi == NetworkInfo.State.CONNECTED || wifi == NetworkInfo.State.CONNECTING){
Log.e("WIFI","Wi-Fi已打開!");
}
//獲取網(wǎng)絡(luò)類型
switch (getNetWorkType(context)){
case Constants.NETTYPE_2G:
ToastUtils.showShort(context,"當(dāng)前網(wǎng)絡(luò)是2G網(wǎng)絡(luò)");
break;
case Constants.NETTYPE_3G:
ToastUtils.showShort(context,"當(dāng)前網(wǎng)絡(luò)是3G網(wǎng)絡(luò)");
break;
case Constants.NETTYPE_4G:
ToastUtils.showShort(context,"當(dāng)前網(wǎng)絡(luò)是4G網(wǎng)絡(luò)");
break;
case Constants.NETTYPE_WIFI:
ToastUtils.showShort(context,"當(dāng)前網(wǎng)絡(luò)是wifi");
break;
}
}
3.5、獲取網(wǎng)絡(luò)類型
/**
* 獲取網(wǎng)絡(luò)類型
* @param context Context
* @return true 表示網(wǎng)絡(luò)可用
*/
public static int getNetWorkType(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
String type = networkInfo.getTypeName();
if (type.equalsIgnoreCase("WIFI")) {
return Constants.NETTYPE_WIFI;//Wi-Fi網(wǎng)絡(luò)
} else if (type.equalsIgnoreCase("MOBILE")) {
NetworkInfo mobileInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (mobileInfo != null) {
switch (mobileInfo.getType()) {
case ConnectivityManager.TYPE_MOBILE:// 手機(jī)網(wǎng)絡(luò)
switch (mobileInfo.getSubtype()) {
//--------------------Added in API level 1---------------------
//(3G)聯(lián)通 ~ 400-7000 kbps
case TelephonyManager.NETWORK_TYPE_UMTS:
//(2.5G) 移動(dòng)和聯(lián)通 ~ 100 kbps
case TelephonyManager.NETWORK_TYPE_GPRS:
//(2.75G) 2.5G 到 3G 的過渡 移動(dòng)和聯(lián)通 ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_EDGE:
//-----------------Added in API level 4---------------------
//( 3G )電信 ~ 400-1000 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_0:
//(2G 電信) ~ 14-64 kbps
case TelephonyManager.NETWORK_TYPE_CDMA:
//(3.5G) 屬于3G過渡 ~ 600-1400 kbps
case TelephonyManager.NETWORK_TYPE_EVDO_A:
//( 2G ) ~ 50-100 kbps
case TelephonyManager.NETWORK_TYPE_1xRTT:
//---------------------Added in API level 5--------------------
//(3.5G ) ~ 2-14 Mbps
case TelephonyManager.NETWORK_TYPE_HSDPA:
//( 3.5G ) ~ 1-23 Mbps
case TelephonyManager.NETWORK_TYPE_HSUPA:
//( 3G ) 聯(lián)通 ~ 700-1700 kbps
case TelephonyManager.NETWORK_TYPE_HSPA:
//---------------------Added in API level 8---------------------
//(2G ) ~25 kbps
case TelephonyManager.NETWORK_TYPE_IDEN:
return Constants.NETTYPE_2G;
//---------------------Added in API level 9---------------------
//3G-3.5G ~ 5 Mbps
case TelephonyManager.NETWORK_TYPE_EVDO_B:
//---------------------Added in API level 11--------------------
//(4G) ~ 10+ Mbps
case TelephonyManager.NETWORK_TYPE_LTE:
return Constants.NETTYPE_4G;
//3G(3G到4G的升級(jí)產(chǎn)物) ~ 1-2 Mbps
case TelephonyManager.NETWORK_TYPE_EHRPD:
//--------------------Added in API level 13-------------------
//( 3G ) ~ 10-20 Mbps
case TelephonyManager.NETWORK_TYPE_HSPAP:
return Constants.NETTYPE_3G;
//無網(wǎng)絡(luò)
default:
return Constants.NETTYPE_NONE;
}
}
}
}
}
return Constants.NETTYPE_NONE;
}
3.6、設(shè)置網(wǎng)絡(luò)提示
/**
* 網(wǎng)絡(luò)為連接時(shí),設(shè)置網(wǎng)絡(luò)
*/
private static void setNetWork(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("網(wǎng)絡(luò)連接提示").setMessage("網(wǎng)絡(luò)不可用,如果繼續(xù),請(qǐng)?jiān)O(shè)置網(wǎng)絡(luò)");
builder.setPositiveButton("設(shè)置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = null;
/**
* 判斷手機(jī)系統(tǒng)的版本!如果 API 大于 10 就是 3.0+
* 因?yàn)?3.0 以上的版本的設(shè)置和 3.0 以下的設(shè)置不一樣,調(diào)用的方法不同
*/
if (Build.VERSION.SDK_INT > 10){
intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
}else {
intent = new Intent();
ComponentName componentName = new ComponentName(
"com.android.settings",
"com.android.settings.WirelessSettings"
);
intent.setComponent(componentName);
intent.setAction("android.intent.action.VIEW");
}
//啟動(dòng)打開 Wi-Fi 設(shè)置頁面
context.startActivity(intent);
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
return;
}
});
builder.create();
builder.show();
}
在 Presenter 層調(diào)用該判斷網(wǎng)絡(luò)請(qǐng)求進(jìn)行登錄操作。具體代碼在 MVP 和 MVC 中詳細(xì)講過,這里不重復(fù)粘貼代碼了。
@Override
public boolean checkUserNameLogin(String userName, String password) {
if (OtherUtils.isUsernameVaild(userName)) {
if (OtherUtils.isPasswordValid(password)) {
if (OtherUtils.checkNetWorkState(mLoginView.getContext())) {
return true;
} else {
mLoginView.showMsg("當(dāng)前無網(wǎng)絡(luò)連接!");
}
} else {
mLoginView.passwordError("密碼過短!");
}
} else {
mLoginView.usernameError("用戶名不符合規(guī)范!");
}
mLoginView.dismissLoading();
return false;
}
四、ConnectivityManager詳解
看到上例中主要使用到的兩個(gè)類是 ConnectivityManager 和 NetworkInfo,下面先看看 ConnectivityManager 的主要參數(shù)和方法。
ConnectivityManager 是網(wǎng)絡(luò)連接相關(guān)的管理器,它主要用于查詢網(wǎng)絡(luò)狀態(tài)并在網(wǎng)絡(luò)發(fā)生改變時(shí)發(fā)出狀態(tài)變化通知。這個(gè)類主要負(fù)責(zé)的下列四個(gè)方面:
1、監(jiān)控網(wǎng)絡(luò)狀態(tài)(包括WiFi, GPRS, UMTS等)。
2、當(dāng)網(wǎng)絡(luò)連接改變時(shí)發(fā)送廣播 Intent。
3、 當(dāng)一種網(wǎng)絡(luò)斷開時(shí),試圖連接到另一種網(wǎng)絡(luò)進(jìn)行故障處理。
4、 提供一系列接口讓應(yīng)用程序查詢可獲得的網(wǎng)絡(luò)的粗粒度和細(xì)粒度狀態(tài)。
ConnectivityManager 比較重要的幾個(gè)類常量
| 類型 | 常量值 | 說明 |
|---|---|---|
| int | TYPE_BLUETOOTH | 藍(lán)牙數(shù)據(jù)連接 |
| int | TYPE_ETHERNET | 以太網(wǎng)數(shù)據(jù)連接 |
| int | TYPE_MOBILE | 移動(dòng)數(shù)據(jù)連接 |
| int | TYPE_WIFI | Wi-Fi連接 |
| int | DEFAULT_NETWORK_PREFERENCE | 默認(rèn)網(wǎng)絡(luò)連接偏好,建議在config.xml中進(jìn)行配置.并通過調(diào)用 getNetworkPreference() 獲取應(yīng)用的當(dāng)前設(shè)置值。 |
| String | EXTRA_EXTRA_INFO | 查詢關(guān)鍵字,提供關(guān)于網(wǎng)絡(luò)狀態(tài)的信息 |
| String | EXTRA_NETWORK_INFO | 建議使用getActiveNetworkInfo() or getAllNetworkInfo()獲取網(wǎng)絡(luò)連接信息 |
| String | EXTRA_NETWORK_TYPE | 觸發(fā) CONNECTIVITY_ACTION廣播的網(wǎng)絡(luò)類型 |
ConnectivityManager 比較重要的幾個(gè)方法
| 返回類型 | 方法名 |
|---|---|
| NetworkInfo | getActiveNetworkInfo() 獲取當(dāng)前連接可用的網(wǎng)絡(luò) |
| NetworkInfo[] | getAllNetworkInfo() 獲取設(shè)備支持的所有網(wǎng)絡(luò)類型的鏈接狀態(tài)信息。 |
| NetworkInfo | getNetworkInfo(int networkType) 獲取特定網(wǎng)絡(luò)類型的鏈接狀態(tài)信息 |
| int | getNetworkPreference() 獲取當(dāng)前偏好的網(wǎng)絡(luò)類型。 |
| boolean | isActiveNetworkMetered() 返回當(dāng)前被計(jì)量的活動(dòng)的數(shù)據(jù)網(wǎng)絡(luò) |
| static boolean | isNetworkTypeValid(int networkType) 判斷給定的數(shù)值是否表示一種網(wǎng)絡(luò) |
| boolean | requestRouteToHost(int networkType, int hostAddress) 確保存在通過指定網(wǎng)絡(luò)接口將流量傳輸?shù)街付ㄖ鳈C(jī)的網(wǎng)絡(luò)路由。 |
| void | setNetworkPreference(int preference) 指定首選網(wǎng)絡(luò)類型 |
| int | startUsingNetworkFeature(int networkType, String feature) 告訴呼叫者想要開始使用命名功能的底層網(wǎng)絡(luò)系統(tǒng)。 |
| int | stopUsingNetworkFeature(int networkType, String feature) 告訴使用命名功能調(diào)用者完成的底層網(wǎng)絡(luò)系統(tǒng)。 |
五、NetworkInfo 詳解
NetworkInfo 是一個(gè)描述網(wǎng)絡(luò)狀態(tài)的接口,可通過 ConnectivityManager 調(diào)用 getActiveNetworkInfo() 獲得當(dāng)前連接的網(wǎng)絡(luò)類型。
NetworkInfo 有兩個(gè)枚舉類型的成員變量 NetworkInfo.DetailedState 和 NetworkInfo.State,用于查看當(dāng)前網(wǎng)絡(luò)的狀態(tài)。其中 NetworkInfo.State的值包括:
| 類型 | 狀態(tài)值 | 說明 |
|---|---|---|
| NetworkInfo.State | CONNECTED | 已連接 |
| NetworkInfo.State | CONNECTING | 正在連接 |
| NetworkInfo.State | DISCONNECTED | 已斷開鏈接 |
| NetworkInfo.State | DISCONNECTING | 正在斷開鏈接 |
| NetworkInfo.State | SUSPENDED | 暫停/掛起 |
| NetworkInfo.State | UNKNOWN | 未知 |
NetworkInfo 還包括一系列可用的方法用于判斷當(dāng)前網(wǎng)絡(luò)是否可用,如下:
| 返回類型 | 方法名 |
|---|---|
| NetworkInfo.DetailedState | getDetailedState() 獲取當(dāng)前細(xì)粒度的網(wǎng)絡(luò)狀態(tài)。 |
| String | getExtraInfo() 報(bào)告關(guān)于網(wǎng)絡(luò)狀態(tài)的額外信息( 如果有的話由下層網(wǎng)絡(luò)層提供),如果有一個(gè)可用 |
| String | getReason() 如果數(shù)據(jù)網(wǎng)絡(luò)連接可用,但是連接失敗,則通過此方法可獲得嘗試鏈接失敗的原因 |
| boolean | isAvailable() 判斷當(dāng)前網(wǎng)絡(luò)是否可用 |
| boolean | isConnected() 判斷當(dāng)前網(wǎng)絡(luò)是否存在,并可用于數(shù)據(jù)傳輸 |
| boolean | isConnectedOrConnecting() 判斷網(wǎng)絡(luò)已連接或正在連接 |
| boolean | isFailover() 指示當(dāng)前試圖連接到的網(wǎng)絡(luò)是否由 ConnectivityManager 嘗試斷開與其它網(wǎng)絡(luò)的連接 |
| String | toString() 返回一個(gè)包含該網(wǎng)絡(luò)的簡單的易懂的字符串描述。 |
一般來說很少用到所有的內(nèi)容,上面也只是寫出一些可能用到的方法和變量。
具體使用在文章前面已經(jīng)寫了,主要是判斷網(wǎng)絡(luò)是否連接,判斷網(wǎng)絡(luò)類型,獲取網(wǎng)絡(luò)類型等。
每個(gè)程序猿必備的110本經(jīng)典編程書,免費(fèi)領(lǐng)取地址:http://mp.weixin.qq.com/s/cx433vAj_CDLzmhOoUS6zA
參考:
https://my.oschina.net/lzan13/blog/133092
http://blog.csdn.net/ls703/article/details/45823485
http://blog.csdn.net/nanzhiwen666/article/details/8288433#
http://blog.csdn.net/oyangyujun/article/details/41723865