ניהול השימוש ברשת

השיעור הזה מתאר כיצד לכתוב אפליקציות עם בקרה פרטנית על השימוש שלהם במשאבי רשת. אם האפליקציה מבצעת ברשת, יש לספק הגדרות משתמש שמאפשרות למשתמשים לשלוט להרגלים של האפליקציה שלך לגבי השימוש בנתונים, כמו התדירות שבה האפליקציה מסנכרנת נתונים, לבצע העלאות/הורדות רק כשיהיה חיבור לרשת Wi-Fi, האם להשתמש בנתונים בזמן נדידה, וכן הלאה. כשאמצעי הבקרה האלו זמינים להם, יש סיכוי הרבה יותר נמוך שהמשתמשים להשבית את הגישה של האפליקציה לנתוני רקע כשהיא מתקרבת למגבלות שלה, כי הן יכולות לשלוט באופן מדויק בכמה נתונים שהאפליקציה שלכם משתמשת בהם.

למידע נוסף על השימוש של האפליקציה שלך ברשת, כולל מספר סוגים של חיבורי רשת לאורך תקופה, קראו את המאמר אפליקציות ובדיקת התנועה ברשת באמצעות רשת הכלי לניתוח ביצועים (profiler). לקבלת הנחיות כלליות בנוגע כתיבת אפליקציות שממזערות את ההשפעה על חיי הסוללה של הורדות ורשת חיבורים, מידע נוסף זמין בקטע אופטימיזציה של חיי הסוללה העברת נתונים מבלי לרוקן את הסוללה.

אפשר גם לבדוק את NetworkConnect דוגמה.

בדיקת החיבור של מכשיר לרשת

למכשיר יכולים להיות כמה סוגים של חיבורי רשת. השיעור הזה מתמקד באמצעות חיבור Wi-Fi או חיבור לרשת סלולרית. לרשימה המלאה של הסוגים האפשריים של רשתות, ConnectivityManager

חיבור Wi-Fi בדרך כלל מהיר יותר. כמו כן, נעשה שימוש בדרך כלל בנתונים סלולריים, דבר שעלול להיות יקרות. אחת מהשיטות הנפוצות לאפליקציות היא לאחזר כמות גדולה של נתונים רק אם מתבצע חיבור ל-Wi-Fi הרשת זמינה.

לפני שמבצעים פעולות ברשת, כדאי לבדוק את המצב של קישוריות רשת. בין היתר, הפעולה הזו יכולה למנוע מהאפליקציה משתמשים בטעות ברדיו שגוי. אם אין חיבור לרשת, שהאפליקציה אמורה להגיב בצורה חיננית. כדי לבדוק את החיבור לרשת, בדרך כלל משתמשים במחלקות הבאות:

  • ConnectivityManager: תשובות לשאילתות לגבי מצב הרשת קישוריות. הוא גם שולח התראה לאפליקציות כשיש קישוריות רשת שינויים.
  • NetworkInfo: מתאר את הסטטוס של רשת מסוג 'רשת סלולרית' או 'רשת Wi-Fi'.

קטע הקוד הזה בודק את קישוריות הרשת ל-Wi-Fi ולמכשירים ניידים. הוא קובע האם ממשקי הרשת האלה זמינים (כלומר, האם ממשקי הרשת היא אפשרית) ו/או מחובר (כלומר, האם רשת קיימת קישוריות ואם ניתן ליצור שקעים ולהעביר נתונים:

Kotlin

private const val DEBUG_TAG = "NetworkStatusExample"
...
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
var isWifiConn: Boolean = false
var isMobileConn: Boolean = false
connMgr.allNetworks.forEach { network ->
    connMgr.getNetworkInfo(network).apply {
        if (type == ConnectivityManager.TYPE_WIFI) {
            isWifiConn = isWifiConn or isConnected
        }
        if (type == ConnectivityManager.TYPE_MOBILE) {
            isMobileConn = isMobileConn or isConnected
        }
    }
}
Log.d(DEBUG_TAG, "Wifi connected: $isWifiConn")
Log.d(DEBUG_TAG, "Mobile connected: $isMobileConn")

Java

private static final String DEBUG_TAG = "NetworkStatusExample";
...
ConnectivityManager connMgr =
        (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isWifiConn = false;
boolean isMobileConn = false;
for (Network network : connMgr.getAllNetworks()) {
    NetworkInfo networkInfo = connMgr.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        isWifiConn |= networkInfo.isConnected();
    }
    if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
        isMobileConn |= networkInfo.isConnected();
    }
}
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);

שימו לב: אל תבססו החלטות על סטטוס הרשת 'זמינה'. שלך צריך לבדוק תמיד isConnected() לפני ביצוע פעולות רשת, מכיוון ש-isConnected() מטפל במקרים כמו שפה לא תקינה רשתות סלולריות, מצב טיסה ונתוני רקע מוגבלים.

דרך תמציתית יותר לבדוק אם ממשק רשת זמין. עוקבים. השיטה getActiveNetworkInfo() מחזירה מופע של NetworkInfo שמייצג את ממשק הרשת המחוברת הראשונה שהוא יכול למצוא, או null אם אף אחד מהממשקים לא מחובר (כלומר, אין חיבור לאינטרנט ):

Kotlin

fun isOnline(): Boolean {
    val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo
    return networkInfo?.isConnected == true
}

Java

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

כדי לבדוק מצב פרטני יותר, אפשר להשתמש NetworkInfo.DetailedState אבל זה בדרך כלל לא נחוץ.

ניהול השימוש ברשת

אפשר להטמיע פעילות העדפות שמעניקה למשתמשים שליטה מפורשת השימוש של האפליקציה במשאבי רשת. לדוגמה:

  • ייתכן לאפשר למשתמשים להעלות סרטונים רק כשהמכשיר מחובר רשת Wi-Fi.
  • יכול להיות שיתבצע סנכרון (או לא), בהתאם לקריטריונים ספציפיים כמו רשת זמינות, מרווח זמן וכן הלאה.

כדי לכתוב אפליקציה שתומכת בגישה לרשת ובניהול השימוש ברשת, צריך למניפסט צריכות להיות ההרשאות ומסנני Intent המתאימים.

  • המניפסט שמובא בהמשך הקטע הזה כולל את הפרטים הבאים הרשאות:
  • אפשר להצהיר על מסנן Intent עבור ACTION_MANAGE_NETWORK_USAGE כדי לציין שהאפליקציה שלך מגדירה פעילות שמציעה אפשרויות לשליטה בשימוש בנתונים. ההגדרות של ACTION_MANAGE_NETWORK_USAGE מוצגות לניהול השימוש בנתוני רשת של אפליקציה ספציפית. כשהאפליקציה שלך כולל פעילות הגדרות שמאפשרת למשתמשים לשלוט בשימוש ברשת, עליך להצהיר על מסנן ה-Intent הזה עבור הפעילות הזו.

באפליקציה לדוגמה, הפעולה הזו מטופלת על ידי הכיתה SettingsActivity, עם ממשק משתמש של העדפות שמאפשר למשתמשים להחליט מתי מורידים פיד.

<?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>

אפליקציות שעוסקות בנתונים רגישים של משתמשים, שמטרגטות את Android 11 וגם גבוה יותר, יכול להעניק גישה לרשת לכל תהליך. על ידי ציון מפורש לתהליכים מורשים גישה לרשת, לבודד את כל הקוד שלא צריך להעלות נתונים.

אמנם לא מובטח למנוע מהאפליקציה להעלות נתונים בטעות, אך מספק דרך לצמצם את הסיכוי שבאגים באפליקציה יגרמו דליפת נתונים.

בדוגמה הבאה מוצגת דוגמה לקובץ מניפסט שנעשה בו שימוש לפי תהליך פונקציונליות:

<processes>
    <process />
    <deny-permission android:name="android.permission.INTERNET" />
    <process android:process=":withoutnet1" />
    <process android:process="com.android.cts.useprocess.withnet1">
        <allow-permission android:name="android.permission.INTERNET" />
    </process>
    <allow-permission android:name="android.permission.INTERNET" />
    <process android:process=":withoutnet2">
        <deny-permission android:name="android.permission.INTERNET" />
    </process>
    <process android:process="com.android.cts.useprocess.withnet2" />
</processes>

יישום פעילות של העדפות

כפי שניתן לראות בקטע המניפסט מוקדם יותר בנושא זה, פעילות SettingsActivity כוללת מסנן Intent עבור פעולת ACTION_MANAGE_NETWORK_USAGE. SettingsActivity הוא תת-מחלקה של PreferenceActivity. הוא מציגה מסך העדפות (מוצג באיור 1) שמאפשר למשתמשים לציין הבאים:

  • האם להציג סיכומים לכל רשומה של פיד XML, או רק קישור לכל רשומה.
  • האם להוריד את פיד XML אם יש חיבור רשת זמין, או רק אם ניתן להתחבר לרשת Wi-Fi זמינים.

חלונית ההעדפות הגדרה של העדפת רשת

איור 1. פעילות העדפות.

הנה SettingsActivity. לתשומת ליבכם: OnSharedPreferenceChangeListener כשמשתמש משנה העדפה, היא מופעלת onSharedPreferenceChanged() שמגדיר את refreshDisplay כ-TRUE. פעולה זו תגרום לרענון התצוגה כאשר המשתמש חוזר לפעילות הראשית:

Kotlin

class SettingsActivity : PreferenceActivity(), OnSharedPreferenceChangeListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

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

    override fun onResume() {
        super.onResume()

        // Registers a listener whenever a key changes
        preferenceScreen?.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
    }

    override fun 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().
        preferenceScreen?.sharedPreferences?.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 fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
        // 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
    }
}

Java

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), האפליקציה מורידה את הפיד ומרעננים את מסך.

Kotlin

class NetworkActivity : Activity() {

    // The BroadcastReceiver that tracks network connectivity changes.
    private lateinit var receiver: NetworkReceiver

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

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

    public override fun onDestroy() {
        super.onDestroy()
        // Unregisters BroadcastReceiver when app is destroyed.
        this.unregisterReceiver(receiver)
    }

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

    public override fun onStart() {
        super.onStart()

        // Gets the user's network preference settings
        val 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.
    fun updateConnectedFlags() {
        val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

        val activeInfo: NetworkInfo? = connMgr.activeNetworkInfo
        if (activeInfo?.isConnected == true) {
            wifiConnected = activeInfo.type == ConnectivityManager.TYPE_WIFI
            mobileConnected = activeInfo.type == ConnectivityManager.TYPE_MOBILE
        } else {
            wifiConnected = false
            mobileConnected = false
        }
    }

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

    companion object {

        const val WIFI = "Wi-Fi"
        const val ANY = "Any"
        const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort;=newest"

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

        // The user's current network preference setting.
        var sPref: String? = null
    }
...

}

Java

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, שקובע את סטטוס החיבור לרשת ומגדיר את הדגלים את wifiConnected ואת mobileConnected לערך True/false בהתאם. התוצאה היא שבפעם הבאה שהמשתמש יחזור לאפליקציה, האפליקציה תוריד רק את הפיד האחרון ועדכון התצוגה אם האפשרות NetworkActivity.refreshDisplay מוגדרת true.

הגדרה של BroadcastReceiver שמקבלת שיחות שלא לצורך, עלולה לרוקן את הסוללה במשאבי המערכת. האפליקציה לדוגמה רושמת את BroadcastReceiver NetworkReceiver אינץ' onCreate(), והיא מבטלת את הרישום onDestroy(). זה יותר קל יותר מהצהרה על <receiver> במניפסט. אחרי ש יש להצהיר על <receiver> במניפסט, הוא יכול להוציא את האפליקציה ממצב שינה בכל שלב גם אם לא הפעלת אותו במשך שבועות. על ידי הרשמה וביטול הרישום NetworkReceiver בתוך הפעילות הראשית, מוודאים שהאפליקציה לא להתעורר אחרי שהמשתמש עוזב את האפליקציה. אם אתם מצהירים על <receiver> במניפסט, ואתם יודעים בדיוק איפה אתם צריכים אותו, יכול להשתמש setComponentEnabledSetting() כדי להפעיל ולהשבית אותו לפי הצורך.

הנה NetworkReceiver:

Kotlin

class NetworkReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val conn = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkInfo: NetworkInfo? = conn.activeNetworkInfo

        // 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 == sPref && networkInfo?.type == 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 == 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()
        }
    }
}

Java

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();
        }
    }
}