این درس نحوه نوشتن برنامههایی را شرح میدهد که کنترل دقیقی بر استفادهشان از منابع شبکه دارند. اگر برنامهتان عملیات شبکه زیادی انجام میدهد، باید تنظیمات کاربری ارائه دهید که به کاربران اجازه دهد عادتهای داده برنامهتان را کنترل کنند، مثلاً اینکه برنامهتان هر چند وقت یکبار دادهها را همگامسازی کند، آیا بارگذاری/بارگیری فقط هنگام اتصال به 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>
برنامههایی که با دادههای حساس کاربر سروکار دارند و Android نسخه ۱۱ و بالاتر را هدف قرار میدهند، میتوانند دسترسی به شبکه را برای هر فرایند اعطا کنند. با مشخص کردن صریح اینکه کدام فرایندها اجازه دسترسی به شبکه را دارند، همه کدهایی را که نیازی به بارگذاری داده ندارند جدا میکنید.
اگرچه تضمین نمیشود که این کار از بارگذاری تصادفی دادهها توسط برنامهتان جلوگیری کند، اما راهی برای کاهش احتمال وجود اشکال در برنامهتان که باعث نشت دادهها میشود ارائه میدهد.
در زیر نمونهای از فایل مانیفست که از عملکرد «بهازای هر فرایند» استفاده میکند نشان داده شده است:
<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 است. این
صفحه اولویتها را نمایش میدهد (در شکل ۱ نشان داده شده است) که به کاربران اجازه میدهد
موارد زیر را مشخص کنند:
- اینکه خلاصه هر ورودی فید XML نمایش داده شود یا فقط پیوند هر ورودی.
- اینکه فید XML درصورت دردسترس بودن هرگونه اتصال شبکه بارگیری شود یا فقط درصورت دردسترس بودن Wi-Fi.

شکل ۱. فعالیت اولویتها.
این SettingsActivity است. توجه داشته باشید که این برنامه
OnSharedPreferenceChangeListener را پیادهسازی میکند.
وقتی کاربر اولویتی را تغییر میدهد،
onSharedPreferenceChanged()
راهاندازی میشود که refreshDisplay را روی درست تنظیم میکند. این کار باعث میشود وقتی کاربر به فعالیت اصلی برمیگردد،
نمایشگر بازآوری شود:
کاتلین
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 را براساس آن روی درست/نادرست تنظیم میکند. نتیجه این است که دفعه بعد که کاربر به برنامه برمیگردد، برنامه فقط آخرین فید را بارگیری میکند و اگر 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(); } } }