Android 网络连接探索(二)

如果应用有大量网络操作,则应该允许让用户设置喜好,例如应用同步数据的频率,是否仅在使用 Wi-Fi 时执行上传/下载,是否在漫游时使用数据,等等。

一、检查设备的网络连接

设备可以具有各种类型的网络连接,Wi-Fi 或者移动网络。常见策略是仅在 Wi-Fi 网络可用时才获取大数据。

在执行网络操作之前​​,最好检查网络连接状态。要检查网络连接,通常使用以下类:

  • ConnectivityManager:响应有关网络连接状态的查询。它还会在网络连接发生变化时通知应用。

  • NetworkInfo:描述给定类型的网络接口的状态(当前是移动网络或 Wi-Fi)。

下面代码测试 Wi-Fi 和移动网络的网络连接。它确定这些网络接口是否可用(即是否可以进行网络连接)和是否连接(即是否存在网络连接以及是否可以建立套接字和传递数据):

private static final String DEBUG_TAG = "NetworkStatusExample";
...
ConnectivityManager connMgr = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
boolean isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
boolean isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);

在执行网络操作之前​​,应该始终调用 isConnected() 进行检查,因为 isConnected() 处理如片状移动网络,飞行模式和受限制的后台数据之类的情况。

检查网络接口是否可用,更简洁方法如下。该 getActiveNetworkInfo() 方法返回一个 NetworkInfo 实例,表示它可以找到的第一个连接的网络接口,若返回 null 则没有任何连接的接口,意味着网络不可用:

public boolean isOnline() {
    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    return (networkInfo != null && networkInfo.isConnected());
}  

二、管理网络使用情况

可以实现一个首选项 Activity 使用户可以显式控制对网络资源的使用情况。例如:

  • 你可能只允许用户在设备连接到 Wi-Fi 网络时上传视频。

  • 你可以根据特定条件(例如网络可用性,时间间隔等)同步数据(或不同步)。

要让应用支持网络访问和管理网络,清单文件必须具有正确的权限和意图过滤器。

下面摘录的清单包括以下权限:

  • android.permission.INTERNET - 允许应用程序打开网络套接字。

  • android.permission.ACCESS_NETWORK_STATE - 允许应用程序访问有关网络的信息。

你可以声明意图过滤器操作 ACTION_MANAGE_NETWORK_USAGE(在 Android 4.0 中引入)来指示某个 Activity 可以设置数据使用。

ACTION_MANAGE_NETWORK_USAGE 显示管理网络数据使用情况的设置。在示例应用程序中,此操作由 SettingsActivity 类处理,该类显示首选项 UI 来让用户决定何时下载订阅源。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.networkusage"
    ...>

    <uses-sdk android:minSdkVersion="4"
           android:targetSdkVersion="14" />

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

    <application
        ...>
        ...
        <activity android:label="SettingsActivity" android:name=".SettingsActivity">
             <intent-filter>
                <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
                <category android:name="android.intent.category.DEFAULT" />
          </intent-filter>
        </activity>
    </application>
</manifest>

三、实现首选项 Activity

SettingsActivity 是 PreferenceActivity 的子类,它显示一个首选项屏幕来让用户指定以下内容:

  • 显示每个 XML 订阅源条目的摘要,还是仅显示每个条目的链接。

  • 是否使用任意可用网络下载 XML 源,或仅在 Wi-Fi 可用时下载 XML 源。

SettingsActivity 的代码如下,它实现了 OnSharedPreferenceChangeListener。当用户更改首选项时,它将触发 onSharedPreferenceChanged(),这会设置 refreshDisplay 为 true,导致用户返回主 Activity 时刷新界面:

public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Loads the XML preferences file
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Registers a listener whenever a key changes
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    protected void onPause() {
        super.onPause();

       // Unregisters the listener set in onResume().
       // It's best practice to unregister listeners when your app isn't using them to cut down on
       // unnecessary system overhead. You do this in onPause().
       getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }

    // When the user changes the preferences selection,
    // onSharedPreferenceChanged() restarts the main activity as a new
    // task. Sets the refreshDisplay flag to "true" to indicate that
    // the main activity should update its display.
    // The main activity queries the PreferenceManager to get the latest settings.

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // Sets refreshDisplay to true so that when the user returns to the main
        // activity, the display refreshes to reflect the new settings.
        NetworkActivity.refreshDisplay = true;
    }
}

四、响应偏好变化

当用户在设置中更改首选项时,通常会对应用的行为产生影响。在下面的代码段中,应用会在 onStart() 中检查首选项设置。如果设置与设备的网络连接状态相匹配(如果设置为 "Wi-Fi" 且设备具有 Wi-Fi 连接),则应用会下载源并刷新显示。

public class NetworkActivity extends Activity {
    public static final String WIFI = "Wi-Fi";
    public static final String ANY = "Any";
    private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";

    // Whether there is a Wi-Fi connection.
    private static boolean wifiConnected = false;
    // Whether there is a mobile connection.
    private static boolean mobileConnected = false;
    // Whether the display should be refreshed.
    public static boolean refreshDisplay = true;

    // The user's current network preference setting.
    public static String sPref = null;

    // The BroadcastReceiver that tracks network connectivity changes.
    private NetworkReceiver receiver = new NetworkReceiver();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Registers BroadcastReceiver to track network connection changes.
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        receiver = new NetworkReceiver();
        this.registerReceiver(receiver, filter);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Unregisters BroadcastReceiver when app is destroyed.
        if (receiver != null) {
            this.unregisterReceiver(receiver);
        }
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.

    @Override
    public void onStart () {
        super.onStart();

        // Gets the user's network preference settings
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi");

        updateConnectedFlags();

        if(refreshDisplay){
            loadPage();
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    public void updateConnectedFlags() {
        ConnectivityManager connMgr = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
        if (activeInfo != null && activeInfo.isConnected()) {
            wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
            mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
        } else {
            wifiConnected = false;
            mobileConnected = false;
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    public void loadPage() {
        if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
                || ((sPref.equals(WIFI)) && (wifiConnected))) {
            // AsyncTask subclass
            new DownloadXmlTask().execute(URL);
        } else {
            showErrorPage();
        }
    }
...

}

五、检测连接变化

剩下只需完成 BroadcastReceiver 的子类 NetworkReceiver。当设备的网络连接发生变化时,NetworkReceiver 截获操作 CONNECTIVITY_ACTION,确定当前网络连接状态,并相应地设置标志 wifiConnectedmobileConnectedtrue / false。用户返回应用时,应用将下载最新的订阅源并在 NetworkActivity.refreshDisplay 设置为 true 时更新界面。

示例应用在 onCreate() 中注册了 NetworkReceiver,并在 onDestroy() 中取消注册 。这比在清单中声明 <receiver> 更轻量。因为这样可以确保在用户离开应用后不会唤醒应用。如果在清单中声明了一个 <receiver> 并且明确知道什么时候需要它,则可以使用 setComponentEnabledSetting() 来启用和禁用该组件。

NetworkReceiver 的代码如下:

public class NetworkReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
    ConnectivityManager conn =  (ConnectivityManager)
        context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = conn.getActiveNetworkInfo();

    // Checks the user prefs and the network connection. Based on the result, decides whether
    // to refresh the display or keep the current display.
    // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
    if (WIFI.equals(sPref) && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // If device has its Wi-Fi connection, sets refreshDisplay
        // to true. This causes the display to be refreshed when the user
        // returns to the app.
        refreshDisplay = true;
        Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();

    // If the setting is ANY network and there is a network connection
    // (which by process of elimination would be mobile), sets refreshDisplay to true.
    } else if (ANY.equals(sPref) && networkInfo != null) {
        refreshDisplay = true;

    // Otherwise, the app can't download content--either because there is no network
    // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
    // is no Wi-Fi connection.
    // Sets refreshDisplay to false.
    } else {
        refreshDisplay = false;
        Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
    }
}

六、优化网络流量消耗

从 Android 7.0(API 级别 24)开始,用户可以在整个设备上启用流量节省程序,以优化其设备的流量消耗,并减少流量消耗。

当用户在 Settings 中启用流量节省程序且设备位于按流量计费的网络上时,系统屏蔽后台流量消耗,同时指示应用在前台尽可能使用较少的数据。用户可以将特定应用加入白名单以允许后台按流量计费的流量消耗,即使在打开流量节省程序时也是如此。

N Developer Preview 扩展 ConnectivityManager API,为应用提供检索用户的流量节省程序首选项和监控首选项变更的方式。

1. 检查流量节省程序首选项

在 N Developer Preview 中,应用可以使用 ConnectivityManager API 来确定正在应用的是哪些流量消耗限制。

getRestrictBackgroundStatus() 方法返回下列值之一:

  • RESTRICT_BACKGROUND_STATUS_DISABLED
    流量节省程序已停用。

  • RESTRICT_BACKGROUND_STATUS_ENABLED
    用户已为此应用启用流量节省程序。应用应努力限制前台流量消耗,并妥善处理后台流量消耗限制。

  • RESTRICT_BACKGROUND_STATUS_WHITELISTED
    用户已启用流量节省程序,但应用在白名单中。应用应努力限制前台和后台流量消耗。

这被认为是在设备连接到按流量计费的网络时限制流量消耗的有效方法,即使流量节省程序被停用或应用在白名单中。

以下示例代码使用 ConnectivityManager.isActiveNetworkMetered()ConnectivityManager.getRestrictBackgroundStatus() 来确定应用应使用多少数据:

ConnectivityManager connMgr = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
// Checks if the device is on a metered network
if (connMgr.isActiveNetworkMetered()) {
  // Checks user’s Data Saver settings.
  switch (connMgr.getRestrictBackgroundStatus()) {
    case RESTRICT_BACKGROUND_STATUS_ENABLED:
    // Background data usage is blocked for this app. Wherever possible,
    // the app should also use less data in the foreground.

    case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
    // The app is whitelisted. Wherever possible,
    // the app should use less data in the foreground and background.

    case RESTRICT_BACKGROUND_STATUS_DISABLED:
    // Data Saver is disabled. Since the device is connected to a
    // metered network, the app should use less data wherever possible.
  }
} else {
  // The device is not on a metered network.
  // Use data as required to perform syncs, downloads, and updates.
}

2. 请求白名单权限

如果应用需要使用后台数据,它可以通过发送一项包含应用软件包名称的 URI 的 Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS Intent 来请求白名单权限:例如 package:MY_APP_ID。

发送 Intent 和 URI 将启动 Settings 应用,还会显示该应用的流量消耗设置。 用户随后可以决定是否启用应用的后台数据。 在发送此 Intent 之前,先询问用户是否希望启用 Settings 应用,以启用后台流量消耗,这是一种有效的做法。

3. 监控流量节省程序首选项变更

应用可以通过创建一个 BroadcastReceiver 以侦听 ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED 以及使用 Context.registerReceiver() 动态注册接收器来监控流量节省程序首选项变更。当应用接收到这条广播时,应通过调用 ConnectivityManager.getRestrictBackgroundStatus() 来检查新的流量节省程序首选项是否会影响其权限。

注:系统只会向使用 Context.registerReceiver() 进行动态注册的应用发送此广播。在其清单中注册接收此广播的应用将不会收到它们。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 应用间通信 应用程式只能间接与设备上的其他应用进行通信。您可以使用AirDrop与其他应用程序共享文件和数据。您还...
    nicedayCoco阅读 697评论 0 1
  • 最近看了几期《最强大脑》,貌似很火。一开始之所以看这个节目,是因为里边出了一位认识的嘉宾——梁冬。 仔细想想,梁冬...
    千山阅读 1,883评论 1 2
  • 前面说了很多废话,说得再好,还不如实践一次。要想使用Git,首先得安装。这次实验主要是实践怎样安装Git。 在Li...
    布吉岛原住民阅读 791评论 0 1
  • 春霞一大早给我发来的信息,她带着发烧几天刚刚好转的两个年幼的孩子,带着帮忙照顾孩子的老人,自己也是刚刚发了一夜的高...
    爱相续阅读 138评论 0 0