این درس نحوه نوشتن برنامه هایی را توضیح می دهد که کنترل دقیقی بر استفاده از منابع شبکه دارند. اگر برنامه شما عملیات شبکه زیادی را انجام می دهد، باید تنظیمات کاربری را ارائه دهید که به کاربران اجازه می دهد عادت های داده برنامه شما را کنترل کنند، مانند اینکه برنامه شما هر چند وقت یکبار داده ها را همگام می کند، آیا آپلود/دانلود را فقط در حالت Wi-Fi انجام می دهد یا نه داده در هنگام رومینگ و غیره. با در دسترس بودن این کنترلها، کاربران با نزدیک شدن به محدودیتهای خود، کمتر امکان دسترسی برنامه شما به دادههای پسزمینه را غیرفعال میکنند، زیرا در عوض میتوانند دقیقاً میزان دادهای را که برنامه شما استفاده میکند کنترل کنند.
برای کسب اطلاعات بیشتر در مورد استفاده از شبکه برنامه خود، از جمله تعداد و انواع اتصالات شبکه در یک دوره زمانی، برنامههای وب را بخوانید و ترافیک شبکه را با نمایهگر شبکه بررسی کنید . برای دستورالعملهای کلی در مورد نحوه نوشتن برنامههایی که تأثیر عمر باتری بارگیریها و اتصالات شبکه را به حداقل میرسانند، به بهینهسازی عمر باتری و انتقال دادهها بدون تخلیه باتری مراجعه کنید.
همچنین می توانید نمونه NetworkConnect را بررسی کنید.
اتصال شبکه دستگاه را بررسی کنید
یک دستگاه می تواند انواع مختلفی از اتصالات شبکه داشته باشد. این درس بر روی استفاده از Wi-Fi یا اتصال شبکه تلفن همراه تمرکز دارد. برای لیست کامل انواع شبکه ممکن، به ConnectivityManager
مراجعه کنید.
Wi-Fi معمولاً سریعتر است. همچنین، داده های تلفن همراه اغلب اندازه گیری می شوند که می تواند گران شود. یک استراتژی رایج برای برنامه ها این است که فقط در صورت وجود شبکه Wi-Fi، داده های بزرگ را واکشی کنند.
قبل از انجام عملیات شبکه، تمرین خوبی است که وضعیت اتصال شبکه را بررسی کنید. از جمله، این می تواند مانع از استفاده ناخواسته برنامه شما از رادیو اشتباه شود. اگر اتصال شبکه در دسترس نیست، برنامه شما باید به خوبی پاسخ دهد. برای بررسی اتصال شبکه، معمولاً از کلاس های زیر استفاده می کنید:
-
ConnectivityManager
: به سوالات مربوط به وضعیت اتصال شبکه پاسخ می دهد. همچنین هنگامی که اتصال شبکه تغییر می کند برنامه ها را مطلع می کند. -
NetworkInfo
: وضعیت یک رابط شبکه از یک نوع معین (در حال حاضر موبایل یا Wi-Fi) را توصیف می کند.
این قطعه کد اتصال شبکه را برای Wi-Fi و تلفن همراه آزمایش می کند. تعیین میکند که آیا این واسطهای شبکه در دسترس هستند (یعنی امکان اتصال به شبکه) و/یا متصل هستند (یعنی اتصال شبکه وجود دارد و آیا امکان ایجاد سوکتها و ارسال داده وجود دارد یا خیر):
کاتلین
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")
جاوا
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
(به این معنی که اتصال اینترنتی در دسترس نیست):
کاتلین
fun isOnline(): Boolean { val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo return networkInfo?.isConnected == true }
جاوا
public boolean isOnline() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); }
برای پرس و جو از وضعیت دقیق تر می توانید از NetworkInfo.DetailedState
استفاده کنید، اما این به ندرت ضروری است.
مدیریت استفاده از شبکه
میتوانید یک فعالیت ترجیحی را پیادهسازی کنید که به کاربران امکان کنترل صریح بر استفاده برنامه شما از منابع شبکه را میدهد. به عنوان مثال:
- ممکن است فقط زمانی که دستگاه به شبکه Wi-Fi متصل است به کاربران اجازه آپلود ویدیوها را بدهید.
- ممکن است بسته به معیارهای خاصی مانند در دسترس بودن شبکه، فاصله زمانی و غیره همگام سازی کنید (یا نه).
برای نوشتن برنامهای که از دسترسی به شبکه و مدیریت استفاده از شبکه پشتیبانی میکند، مانیفست شما باید مجوزها و فیلترهای هدف مناسب را داشته باشد.
- مانیفست که بعداً در این بخش استخراج میشود شامل مجوزهای زیر است:
-
android.permission.INTERNET
- به برنامه های کاربردی اجازه می دهد تا سوکت های شبکه را باز کنند. -
android.permission.ACCESS_NETWORK_STATE
— به برنامه های کاربردی اجازه می دهد به اطلاعات مربوط به شبکه ها دسترسی پیدا کنند.
-
- میتوانید فیلتر هدف را برای عملکرد
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>
برنامههایی که با دادههای حساس کاربر سروکار دارند و اندروید 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 در دسترس باشد.
اینجا SettingsActivity
است. توجه داشته باشید که OnSharedPreferenceChangeListener
را پیاده سازی می کند. هنگامی که کاربر ترجیحی را تغییر میدهد، onSharedPreferenceChanged()
فعال میشود که refreshDisplay
روی true تنظیم میکند. هنگامی که کاربر به فعالیت اصلی باز می گردد، نمایشگر به روز می شود:
کاتلین
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 } }
جاوا
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 باشد)، برنامه فید را دانلود می کند و نمایشگر را تازه می کند.
کاتلین
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 } ... }
جاوا
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
است:
کاتلین
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() } } }
جاوا
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(); } } }