【Android】用 PING 偵測網路連線狀況

參考資料 ----
ConnectivityManager
NetworkInfo
Process
ping(8) - Linux man page

首先授予 app 權限

AndroidManifest.xml
 
...
...

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

...
...
 

activity_main.xml
 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.oldgrayduck.ping.MainActivity">


    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="PING"
        android:id="@+id/btnPing"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:nestedScrollingEnabled="true"
        android:onClick="onbtnPingClicked"/>
</RelativeLayout>
 

一般偵測區域網路連線,只要 ConnectivityManager + NetworkInfo 即可,如下:
 
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connManager.getActiveNetworkInfo();

// 可透過下列指令取得進一步的連線資訊
info.getTypeName();             // 目前以何種方式連線 (WIFI, MOBILE)
info.getState();                // 目前連線狀態 (DISCONNECTED, CONNECTING, CONNECTED)
info.isAvailable();             // 目前網路是否可使用 (true/false)
info.isConnected();             // 網路是否已連接 (true/false)
info.isConnectedOrConnecting(); // 網路是否已連接 或 連線中 (true/false)
info.isFailover();              // 網路目前是否有問題 (true/false)
info.isRoaming();               // 網路目前是否在漫遊中 (true/false)

 

但這不足以得知是否連上網際網路,例如:
一般我們家裡都有無線路由器,當我們把路由器上 WAN 的線拔掉,家裡對外就斷線了,但 Android 設備仍可以連上路由器,則

info.isAvailable() 是 true
info.isConnected() 是 true

這樣無法判斷是否連上網際網路。

Android 的底層是 Linux,一般來說,在 /system/bin/ 下有 ping 這個程式,如果沒有,可能是被該 Android 裝置的製造商移除了;因為是 Linux 的指令,指令參數請參考 Linux;我們可以藉 ping 來偵測網路是否通。

 
...
...

public void onbtnPingClicked(View v)
{
    if (isConnected())
        Toast.makeText(this, "網路已連線", Toast.LENGTH_LONG).show();
    else
        Toast.makeText(this, "斷線中", Toast.LENGTH_LONG).show();
}

private boolean isConnected()
{
    boolean result = false;
    ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = connManager.getActiveNetworkInfo();
    if (info==null || !info.isConnected())
        {
            Log.d(TAG, "網路 不通");
            result = false;
        }
    else
        {
            if(info.isConnected())
                Log.d(TAG, "info connected");
            else
                Log.d(TAG, "info disconnected");
            if (info.isAvailable())
                {
                    try
                    {
                        Log.d(TAG, "info is available");
                        // 不像 Windows 的 ping 預設執行 4 次
                        // Linux 的 ping 預設是一直執行的,所以一定要下參數指定次數
                        // 不然會造成您的 app 無回應

                        // 試著 ping 168.95.1.1, 4 次, 參數 -c 4
                        Process process = new ProcessBuilder().command("/system/bin/ping","-c 4","168.95.1.1")
                                                            .redirectErrorStream(true)
                                                            .start();
                        try
                        {
                            String strPing = "";
                            InputStream in = process.getInputStream();
                            OutputStream out = process.getOutputStream();
                            InputStreamReader reader = new InputStreamReader(in, "utf-8");
                            int i;
                            Log.d(TAG, "read instream");
                            while ((i = in.read()) != -1)
                            {
                                strPing = strPing + (char) i;
                            }
                            out.close();
                            in.close();
                            reader.close();
                            Log.d(TAG, "strPing ==== "+strPing);
                            if(strPing.indexOf("ttl")>=0)
                                {
                                    Log.d(TAG, "strPing 字串中有 ttl");
                                    result = true;
                                }
                            else
                                {
                                    Log.d(TAG, "Ping 沒回應");
                                    result = false;
                                }
                        }
                        catch(Exception e)
                        {
                            result = false;
                        }
                        finally
                        {
                            // 記得要釋放掉 process
                            process.destroy();
                        }
                    }
                    catch (Exception e)
                    {
                        Log.d(TAG, "Exception--> " + e.getMessage());
                        result = false;
                    }
                }
            else
                {
                    Log.d(TAG, "網路不可用");
                    result = false;
                }
        }
    return result;
}

...
...
 

如果網際網路是通的,則 strPing 字串會含有如下的內容:

64 bytes from 168.95.1.1: icmp_seq=1 ttl=249 time=4.67 ms
64 bytes from 168.95.1.1: icmp_seq=2 ttl=249 time=6.55 ms
64 bytes from 168.95.1.1: icmp_seq=3 ttl=249 time=4.29 ms
64 bytes from 168.95.1.1: icmp_seq=4 ttl=249 time=5.27 ms


提醒您:老人家這篇筆記偷懶,並未考慮 app 執行的程式合理性及最佳化,ping 這種耗時的指令,建議您還是放在 ThreadAsyncTask 執行比較好。



相關筆記 ----
【Android】AsyncTask - Thread 外的另一選擇