إدارة استخدام الشبكة

يصف هذا الدرس كيفية كتابة التطبيقات التي تتضمن عناصر تحكم دقيقة على استخدامهم لموارد الشبكة. إذا كان تطبيقك يُجري العديد من عمليات الشبكة، فيجب عليك تقديم إعدادات المستخدم التي تسمح للمستخدمين بالتحكم في عادات البيانات في تطبيقك، مثل عدد مرات مزامنة التطبيق للبيانات، وما إذا كان إجراء عمليات التحميل/التنزيل فقط عند الاتصال بشبكة Wi-Fi أو استخدام البيانات أثناء التجوال وهكذا مع توفر عناصر التحكم هذه لهم، يقل احتمال قيام المستخدمين إيقاف وصول تطبيقك إلى بيانات الخلفية عندما يقترب من الوصول إلى الحدّ الأقصى المسموح به إذ يمكنها بدلاً من ذلك التحكّم بدقة في مقدار البيانات التي يستخدمها تطبيقك.

لمعرفة مزيد من المعلومات حول استخدام التطبيق للشبكة، بما في ذلك رقم أنواع اتصالات الشبكة على مدار فترة زمنية، فاطلع على الويب التطبيقات وفحص حركة مرور الشبكة باستخدام الشبكة محلل البيانات. للحصول على إرشادات عامة حول كيفية كتابة تطبيقات تقلل من تأثير عمر البطارية الناتج عن عمليات التنزيل والشبكات يُرجى الاطّلاع على تحسين عمر البطارية نقل البيانات بدون استنزاف البطارية

يمكنك أيضًا الاطلاع على واجهة برمجة التطبيقات 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.
  • يمكنك إجراء المزامنة (أو عدم المزامنة) استنادًا إلى معايير محددة مثل الشبكة والتوافر والفاصل الزمني وما إلى ذلك.

لكتابة تطبيق يتيح الوصول إلى الشبكة وإدارة استخدام الشبكة، يجب أن يتضمن البيان الأذونات وفلاتر الأهداف الصحيحة.

  • يتضمن البيان المقتطَع لاحقًا في هذا القسم ما يلي: الأذونات:
  • يمكنك تعريف فلتر الأهداف ACTION_MANAGE_NETWORK_USAGE إجراءً للإشارة إلى أن تطبيقك يحدد نشاطًا يقدم للتحكم في استخدام البيانات. تعرض "ACTION_MANAGE_NETWORK_USAGE" الإعدادات لإدارة استخدام بيانات الشبكة لتطبيق معين. عند تثبيت تطبيقك نشاط إعدادات يتيح للمستخدمين التحكم في استخدام الشبكة، فيجب عليك أعلِن عن فلتر الأهداف هذا لهذا النشاط.

في نموذج التطبيق، ينفِّذ الصف هذا الإجراء 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 على فلتر أهداف الإجراء ACTION_MANAGE_NETWORK_USAGE. SettingsActivity هي فئة فرعية من PreferenceActivity أُنشأها جون هنتر، الذي كان متخصصًا شاشة تفضيلات (كما هو موضح في الشكل 1) تتيح للمستخدمين تحديد التالي:

  • تحدّد هذه السمة ما إذا كنت تريد عرض ملخّصات لكل إدخال في خلاصة XML أو مجرد رابط لكل إدخال.
  • تحديد ما إذا كنت تريد تنزيل خلاصة XML في حال توفّر أي اتصال بالشبكة أو عند توفّر اتصال Wi-Fi فقط المتوفرة.

لوحة الإعدادات المفضّلة تعيين تفضيل الشبكة

الشكل 1. نشاط الإعدادات المفضّلة

إليك SettingsActivity. لاحظ أنه ينفذ OnSharedPreferenceChangeListener عندما يغيّر المستخدم أحد الإعدادات المفضّلة، يتم تنشيطه. onSharedPreferenceChanged()، والذي يضبط refreshDisplay على "صحيح". يؤدي هذا إلى تحديث الشاشة عند يعود المستخدم إلى النشاط الرئيسي وهو:

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