مشخصات مقاله
پردازش پس زمینه ای با Handler, AsynchTask و Loader
کلیه حقوق مادی و معنوی این مقاله متعلق به آموزشگاه تحلیل داده می باشد و هر گونه استفاده غیر قانونی از آن پیگرد قانونی دارد.
پردازش پس زمینه ای با Handler, AsynchTask و Loader , آموزش اندروید
مبحث پیش رو نحوه ی استفاده از Thread ها, Handler ها و AsynchTask را در برنامه ی کاربردی آموزش داده و چگونگی مدیریت چرخه ی حیاط اپلیکیشن به همراه نخ ها را تشریح می کند .
فهرست محتوا
1. نخ رابط کاربری اندروید
· نخ اصلی
2. چرا باید از سازه های همزمان سازی (concurrency construct) استفاده کرد ؟
3. استفاده از نخ های جاوا در اندروید
· معایب و کاستی های استفاده از نخ های جاوا ((java thread در اندروید
4. استفاده از سازه های همزمانی در اندروید
5. کلاس Handler
· کاربرد کلاس Handler
· ایجاد و استفاده ی مجدد از نمونه های کلاس Handler
6. کلاس AsynchTask
· کاربرد کلاس AsynchTask
· استفاده از کلاس AsynchTask
· اجرای چندین AsynchTask به طور موازی
· معایب و کاستی های استفاده از کلاس AsynchTask
· نمونه ای از کاربرد کلاس AsynchTask
7. پردازش پس زمینه ای و مدیریت چرخه ی حیات
· ابقا وضعیت / ماندگار کردن داده حین تغییرات پیکربندی
· بکارگیری app object جهت ذخیره سازی اشیإ
8. fragmentها و پردازش پس زمینه ای
· ابقا و حفظ نمونه حین تغییرات در نحوه ی پیکربندی
· الصاق headless fragmentها
9. کلاس Loader
· کاربرد کلاس Loader
· پیاده سازی کلاس Loader
· پایگاه داده ی SQLite و کلاس CursorLoader
10. تمرین استفاده از کلاس Loader سفارشی برای Preferences
· پیاده سازی loader اختصاصی
· تست و اجرای برنامه
11. تمرین : چرخه حیات activity و نخ ها
12. بهینه سازی استفاده از حافظه
· استفاده از حافظه ی پنهان
· استفاده از ساختارهای حافظه ی کارامد
· پاک سازی حافظه
13. StrictMode
14. ارائه ی بازخورد از طریق محاوره
1. نخ رابط کاربری اندروید
نخ اصلی
اندروید اصلاح رابط کاربری و مدیریت رخدادهای ورودی (input event) را از یک نخ رابط کاربری اصلی صورت می دهد که به آن main Thread نیز گفته می شود .
اندروید تمامی رویدادها را در یک صف جمع آوری کرده, سپس یک نمونه از کلاس Looper را پردازش می کند .
2. چرا باید از سازه های همزمانی (concurrency construct) استفاده کرد ؟
در صورتی که برنامه هیچ سازه ی همزمانی بکارنبرد, تمامی کدهای برنامه ی کاربردی اندروید در نخ اصلی اجرا شده و کلیه ی دستورهای یکی پس از دیگری اجرا می شوند .
در صورت اجرای عملیات دراز مدت و طولانی مثل دسترسی به داده از اینترنت, برنامه ی کاربردی مورد نظر تا زمانی که عملیات مربوطه پایان نیافته مسدود می شود .
به منظور بهینه سازی و فراهم کردن بهترین تجربه برای کاربر, تمامی عملیات کند در یک برنامه باید به صورت ناهمگام صورت پذیرند, حال یا به واسطه ی سازه های همزمانی زبان جاوا و یا از طریق android framework . عملیات مذکور می تواند شامل تمامی عملیات کند از جمله دسترسی به شبکه, فایل, داده و همچنین محاسبات پیچیده باشد .
3. استفاده از نخ های جاوا در اندروید
اندروید جهت اجرای پردازش ناهمگام ,قابلیت استفاده از کلاس Thread را پشتیبانی می کند .
اندروید همچنین بسته ی java.util.concurrent را که شامل کلاس های ThreadPools و Executor می شود جهت اجرا و پردازش عملیات در پس زمینه ارائه می دهد .
در صورت نیاز به بروز رسانی رابط کاربری از یک Thread جدید, باید ابتدا آن را با user interface thread (نخ رابط کاربری( همزمان سازی کنید .
معایب استفاده از نخ های جاوا ((java thread در اندروید
در صورت استفاده از نخ های زبان جاوا, برنامه نویس خود باید شرایط و ملزومات زیر را در کد مربوطه مدیریت کند :
· همزمان سازی با نخ اصلی در صورتی که برنامه نویس بخواهد نتایج حاصله را به رابط کاربری بازگرداند
· هیچ پیش فرضی برای لغو یا حذف کردن نخ وجود ندارد
· هیچ فرایند پیش فرض یکپارچه کردن و ادغام نخ ها (thread pooling) وجود ندارد
· هیچ پیش فرضی ویژه ی مدیریت تغییرات پیکربندی در اندروید وجود ندارد
4. استفاده از سازه های همزمانی در اندروید
اندروید در مقایسه با جاوا سازه های مضافی ویژه ی همگام سازی عرضه می کند . می توانید برای این منظور از کلاس های android.os.Handler یا AsyncTasks کمک بگیرید . رویکردهای پیچیده تری نیز وجود دارند که مبتنی بر کلاس Loader, retained Fragment و service ها می باشد .
5. کلاس Handler
کاربرد کلاس Handler
کلاس Handler به شما اجازه می دهد پیام ها و اشیإRunnable را که مربوط به یک صف پیام نخ (thread message queue) می باشد, پردازش و ارسال کنید (در واقع می توان با استفاده از کلاس Handler, یک نخ ثبت کرد و همچنین یک مجرای ساده برای ارسال داده به نخ مزبور ایجاد کرد ) .
توجه داشته باشید که یک شئ Handler تنها خود را با نخی ثبت یا رجیستر می کند که در آن بوجود آمده باشد . برای مثال, چنانچه نمونه ی جدیدی از کلاس Handler را در متد onCreate () اکتیویتی ایجاد کنید, شئ Handler بدست آمده از قابلیت ارسال داده به نخ اصلی برخوردار خواهد بود .
در نتیجه داده ای که از طریق کلاس Handler ارسال می شود, همچنین می تواند نمونه ای از کلاس Message یا Runnable باشد .
نکته : Handler به خصوص زمانی بسیار کارامد است که برنامه نویس بخواهد داده ی مورد نظر را چندین بار به نخ اصلی ارسال کند .
ایجاد و استفاده ی مجدد از نمونه های کلاس Handler
به منظور استفاده از Handler باید ابتدا این کلاس را به ارث برده (subclass), سپس جهت پردازش پیام ها متد handleMessage () را بازنویسی (override) کنید .
Thread قادر است پیام ها را با بهره گیری از متدهای sendMessage(Message) و sendEmptyMessage () به شئ Handler ارسال کند .
برای پردازش Runnable می توان از متد post () کمک گرفت .
جهت اجتناب از ایجاد شئ جدید نیز می توان شئ Handler موجود activity را مورد استفاده قرار داد .
// Reuse existing handler if you don't
// have to override the message processing
handler = getWindow().getDecorView().getHandler();
کلاس View این امکان را برای شما بوجود می آورد که اشیإ از نوع Runnable را به وسیله ی متد post () ارسال کنید .
مثال
کد زیر نحوه ی استفاده از یک Handler از به واسطه ی View نمایش می دهد .
فرض کنید activity شما از شِمای کلی (layout) زیر استفاده می کند :
xml version="1.0" encoding="utf-8" ?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<progressbar android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="10"
android:padding="4dip">
progressbar>
<textview android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="">
textview>
<button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onclick="startProgress"
android:text="Start Progress">button>
linearlayout>
با کد زیر به مجرد اینکه کاربر Button را می زند, ProgressBar بروز رسانی می شود .
package de.vogella.android.handler;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
public class ProgressTestActivity extends Activity {
private ProgressBar progress;
private TextView text;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
progress = (ProgressBar) findViewById(R.id.progressBar1);
text = (TextView) findViewById(R.id.textView1);
}
public void startProgress(View view) {
// do something long
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
final int value = i;
doFakeWork();
progress.post(new Runnable() {
@Override
public void run() {
text.setText("Updating");
progress.setProgress(value);
}
});
}
}
};
new Thread(runnable).start();
}
// Simulating something timeconsuming
private void doFakeWork() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6. کلاس AsynchTask
شئ AsyncTask ایجاد یک فرایند پس زمینه (background process) و پروسه ی همگام سازی (synchronization) را با نخ اصلی (main thread) کپسوله سازی کرده و همچنین از قابلیت ارسال گزارش از پیشرفت و فعالیت تسک های در حال اجرا پشتیبانی می کند .
استفاده از کلاس AsynchTask
در وهله ی اول برای استفاده از کلاس فوقالذکر باید آن را به ارث ببرید . AsyncTask از generic ها و varargها بهره می گیرد. پارامتر های لازمه ی آن به شرح زیر هستند :
AsyncTask
AsyncTask با فراخوانی متد execute () راه اندازی می شود.
متد execute () خود متدهای doInBackground () و onPostExecute () را صدا می زند .
TypeOfVarArgParams به عنوان ورودی / input به داخل متد doInBackground () ارسال می شود, ProgressValue ویژه ی progress info بکار می رود و ResultValue باید از متد doInBackground () بازگردانده شده و به عنوان پارامتر به متد onPostExecute() ارسال شود .
متد doInBackground () دربردارنده ی دستور کدنویسی است که باید در یک نخ پس زمینه اجرا شود . متد ذکر شده در یک thread مجزا به صورت خودکار اجرا می شود .
متد onPostExecute() خود را مجدداً با نخ رابط کاربری همگام کرده و به آن اجزای بروز یا آپدیت شدن را می دهد . این متد به محض اینکه متد doInBackground () خاتمه یافت توسط framework فراخوانده می شود .
اجرای چندین AsynchTask به طور موازی
اندروید تسک های AsynchTask را پیش از نسخه ی 1.6 و دوباره از ویرایش 3.0 به بعد, به صورت پیش فرض به ترتیب اجرا می کند .
برنامه نویس می تواند به اندروید دستور دهد آن را با متد executeOnExecutor() به طور همزمان یا موازی اجرا کرده, AsyncTask.THREAD_POOL_EXECUTOR را به عنوان اولین پارامتر تعریف کند .
تکه کد زیر این امر را نمایش می دهد :
// ImageLoader extends AsyncTask
ImageLoader imageLoader = new ImageLoader(imageView);
// Execute in parallel
imageLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "http://url.com/image.png");
معایب و کاستی های استفاده از کلاس AsynchTask
AsyncTask تغییرات در نحوه ی پیکربندی (config changes) را خود به صورت خودکار اداره نمی کند, به طور مثال چنانچه activity بازسازی شد, برنامه نویس باید خود آن را در کدنویسی اداره و تنظیم کند .
یکی از راه های حل این مشکل تعریف AsynchTask در یک headless fragment retained است . س
نمونه ای از کاربرد کلاس AsynchTask
کد زیر نحوه ی استفاده از کلاس AsynchTask جهت دانلود محتوای یک صفحه وب را برای شما به نمایش می گذارد .
پروژه ی جدیدی به نام de.vogella.android.asynctask و activity ی به نام ReadWebpageAsyncTask ایجاد کرده, سپس مجوز android.permission.INTERNET را به فایل AndroidManifest.xml اضافه کنید .
حال طرح کلی (layout) زیر را ایجاد کنید :
xml version="1.0" encoding="utf-8" ?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<button android:id="@+id/readWebpage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onclick="onClick"
android:text="Load Webpage">button>
<textview android:id="@+id/TextView01"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Placeholder">
textview>
linearlayout>
Activity خود را به ترتیب زیر اصلاح کنید:
package de.vogella.android.asynctask;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import de.vogella.android.asyntask.R;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class ReadWebpageAsyncTask extends Activity {
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView) findViewById(R.id.TextView01);
}
private class DownloadWebPageTask extends AsyncTask
protected String doInBackground(String... urls) {
String response = "";
for (String url : urls) {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse execute = client.execute(httpGet);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null) {
response += s;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return response;
}
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
public void onClick(View view) {
DownloadWebPageTask task = new DownloadWebPageTask();
task.execute(new String[] { "http://www.vogella.com" });
}
}
اکنون اگر برنامه را اجرا کرده و دکمه ی مورد نظر را بزنید, محتوای صفحه وب تعریف شده در پس زمینه خوانده می شود . به محض پایان یافتن پروسه, TextView بروز رسانی می شود .
7. پردازش پس زمینه ای و مدیریت چرخه ی حیات
ابقا وضعیت / ماندگار کردن داده حین تغییرات پیکربندی
یکی از چالش هایی که بر سر راه استفاده از نخ ها قرار می گیرد, در نظر گرفتن چرخه ی حیات (life-cycle) برنامه ی کاربردی است . سیستم اندروید ممکن است activity مربوطه را ناگهان متوقف (kill) کند یا نحوه ی پیکربندی را تغییر دهد که باز هم در نتیجه منجر به بازآغازی activity می شود .
همچنین از آنجایی که محاوره ها به activity ای که آن ها را ایجاد کرده متصل و مرتبط هستند, برنامه نویس باید بهopen dialogue (محاوره ی باز) هم رسیدگی کند . در صورتی که activity مجدداً راه اندازی شد و دستیابی به محاوره ی موجود امکان پذیر شد, شما با استثنای View not attached to window manager مواجه می شوید .
به منظور ذخیره سازی شئ می توانید از متد onRetainNonConfigurationInstance() استفاده کنید . این متد به شما اجازه می دهد تنها یک شئ البته در صورتی که activity قرار است به زودی بازآغازی شود, ذخیره کنید .
جهت بازیابی این شئ کافی است متد getLastNonConfigurationInstance() را بکارببرید, از این طریق شما می توانید در صورتی که activity قرار است مجدد راه اندازی شود, یک شئ (به عنوان مثال یک نخ در حال اجرا / running thread) ذخیره کنید .
اگر activity برای اولین بار راه اندازی شده یا به وسیله ی متد finish () خاتمه یافته, در آن صورت متد getLastNonConfigurationInstance() مقدار null بازمی گرداند .
از رابط برنامه سازی کاربردی یا API 13 به بعد, استفاده از متد onRetainNonConfigurationInstance() دیگر منسوخ تلقی می گردد . هم اکنون روش جایگزین برای ماندگار کردن داده بین تغییرات در نحوه ی پیکربندی, بکارگیری متد setRetainInstance() و fragment ها می باشد .
بکارگیری app object جهت ذخیره سازی اشیإ
اگر قرار است چندین شئ در activity ها و مابین تغییرات مختلف پیکربندی ذخیره شود, در آن صورت می توانید کلاس Application را پیاده سازی کنید .
برای استفاده از کلاس Application, کافی است اسم کلاس (classname) را به صفت مشخصه ی android:name برنامه ی کاربردی خود اختصاص دهید .
کلاس application به صورت خودکار توسط Android runtime ایجاد شده و تا زمانی که کل برنامه کاملاً متوقف نشده یا پایان نیافته, در دسترس خواهد بود .
از این کلاس می توان جهت دسترسی به اشیإیی استفاده کرد که بین تمام activity ها مشترک بوده یا برای کلیه ی چرخه ی حیات activity مربوطه در دسترس می باشد .
برنامه نویس می تواند در متد onCreate() اشیإیی را ایجاد کرده, سپس آن ها را از طریق فیلدهای عمومی (public fields) یا متدهای getter در دسترس قرار دهد .
متد onTerminate() در کلاس application تنها به یک منظور بکار می رود و آن تست کردن است . در صورتی که سیستم اندروید پروسه ای را که برنامه ی کاربردی شما در آن در حال اجرا است خاتمه دهد به دنبال این امر همه ی منابع تخصیص یافته به صورت خودکار آزاد می شوند .
می توان با فراخوانی متد getApplication() در activity به Application دست یافت .
8. fragmentها و پردازش پس زمینه ای
ابقا و حفظ نمونه حین تغییرات در نحوه ی پیکربندی
می توان بدون رابط کاربری از fragment ها استفاده کرده و همچنین با فراخوانی متد setRetainInstance(), آن ها را مابین تغییراتی که در نحوه ی پیکربندی رخ می دهد ماندگار کرد .
بدین وسیله AsynTask و Thread حین تغییر پیکربندی ماندگار می شوند . این امر همچنین برای برنامه نویس این امکان را فراهم می کند که بدون درنظر گرفتن صریح چرخه ی حیات activity پردازش پس زمینه ای انجام دهد .
الصاق headless fragmentها
در صورت انجام پردازش پس زمینه ای شما می توانید یک headless fragment به صورت پویا (dynamic) به اپلیکیشن خود الصاق کرده و متد setRetainInstance(true) را فراخوانی کنید . fragment مزبور بین تغییرات اعمال شده در پیکربندی حفظ یا ماندگار (retain) شده و شما قادر خواهی بود در آن پردازش ناهگام (asynch processing) پیاده کنید .
9. کلاس Loader
کاربرد کلاس Loader
کلاس فوق این قابلیت را به برنامه نویس می دهد که داده را به صورت ناهمگام در یک activity یا fragment بارگذاری کند . از این طریق fragment یا activity قادر خواهند بود بر منبع داده (data source) نظارت داشته و در صورت تغییر محتوا, نتیجه ی جدید ارائه دهند و همچنین داده ها را مابین تغییرات پیکربندی ماندگار کنند .
چنانچه کلاس Loader نتیجه را پس از اینکه شئ از activity یا fragment والد خود منفصل و گسسته (disconnect) شد بازیابی کرد, قادر خواهد بود داده ی مورد نظر را به طور موقت در حافظه ی پنهان (cache) ذخیره کند .
Loader ها بر اولین بار در ویرایش 3.0 اندروید رونمایی شده و جزئی از layer ) compatibilityلایه ی سازگاری) نسخه ی 1.6 به بعد اندروید می باشد .
پیاده سازی کلاس Loader
می توانید کلاس انتزاعی AsyncTaskLoader را به عنوان پایه و اساس پیاده سازی های کلاس Loader بکار ببرید .
LoadManager یک activity یا fragment این قابلیت را دارد که یک یا چند نمونه از کلاس Loader را مدیریت کند . فرایند ایجاد کلاس Loader از طریق فراخوانی متد ذیل صورت می گیرد :
# start a new loader or re-connect to existing one
getLoaderManager().initLoader(0, null, this);
اولین پارامتر یک شناسه (ID) منحصربفرد است که درآینده توسط کلاس بازفراخوان (callback class) به منظور شناسایی Loader مورد استفاده قرار می گیرد . دومین پارامتر یک Bundle است که برای اطلاعات بیشتر به کلاس بازفراخوان داده می شود .
سومین پارامتر initLoader () کلاسی است که به محض آغاز مقداردهی اولیه (callback class) فراخوانده می شود . این کلاس باید رابط LoaderManager.LoaderCallbacks را پیاده سازی کند (به عبارتی دیگر بهتر است activity یا fragment ای که از Loader بهره می گیرد, رابط LoaderManager.LoaderCallbacks را پیاده سازی کند .
Loader خود به طور مستقیم با فراخوانی متد getLoaderManager().initLoader() ایجاد نمی شود, بلکه باید توسط کلاس بازفراخوان داخل متد onCreateLoader() بوجود آید .
پس از اینکه Loader خواندن داده را به طور ناهمگام پایان داد, متد onLoadFinished() از کلاس بازفراخوان صدا زده می شود (اینجا می توانید رابط کاربری خود را آپدیت کنید) .
پایگاه داده ی SQLite و کلاس CursorLoader
اندروید پیاده سازی پیش فرضی را از کلاس Loader جهت کنترل و اداره ی اتصالات به پایگاه داده ی SQLite ارائه می دهد . آن شئ, کلاس CursorLoader است .
ویژه ی ContentProvider مبتنی بر پایگاه داده ی SQLite اغلب از کلاس CursorLoader بهره می گیریم . Loader مذکور query (پرس و جو) را داخل یک نخ پس زمینه انجام می دهد تا بدین وسیله برنامه ی کاربردی مسدود (block) نشود.
کلاس CursorLoader جایگزینی برای cursor هایی است که توسط خود activity مدیریت می شوندActivity-managed) cursors) . Cursor های نام برده هم اکنون منسوخ محسوب می شود.
چنانچه Cursor نامعتبر شد, متد onLoaderReset () برای کلاس بازفراخوان صدا زده می شود .
10. تمرین استفاده از کلاس Loader سفارشی برای Preferences
پیاده سازی loader اختصاصی
در مثال زیر برای مدیریت preferences یک Loader اختصاصی پیاده سازی می کنیم . در هر مرحله ی بارگذاری مقدار preferences افزایش می یابد .
پروژه ی com.vogella.android.loader.preferences را ایجاد کرده و activity آن را MainActivity نام گذاری کنید .
کلاس زیر را به عنوان پیاده سازی اختصاصی AsyncTaskLoader جهت تنظیم و اداره ی preferences مشترک ایجاد کنید .
package com.vogella.android.loader.preferences;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class SharedPreferencesLoader extends AsyncTaskLoader
private SharedPreferences prefs = null;
public static void persist(final SharedPreferences.Editor editor) {
editor.apply();
}
public SharedPreferencesLoader(Context context) {
super(context);
}
// Load the data asynchronously
@Override
public SharedPreferences loadInBackground() {
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.registerOnSharedPreferenceChangeListener(this);
return (prefs);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
// notify loader that content has changed
onContentChanged();
}
/**
* starts the loading of the data
* once result is ready the onLoadFinished method is called
* in the main thread. It loader was started earlier the result
* is return directly
* method must be called from main thread.
*/
@Override
protected void onStartLoading() {
if (prefs != null) {
deliverResult(prefs);
}
if (takeContentChanged() || prefs == null) {
forceLoad();
}
}
}
نمونه کد زیر بکارگیری این Loader را در یک activity نمایش می دهد .
package com.vogella.android.loader.preferences;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Loader;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity implements
LoaderManager.LoaderCallbacks
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.prefs);
getLoaderManager().initLoader(0, null, this);
}
@Override
public Loader
return (new SharedPreferencesLoader(this));
}
@SuppressLint("CommitPrefEdits")
@Override
public void onLoadFinished(Loader
SharedPreferences prefs) {
value += 1;
textView.setText(String.valueOf(value));
// update value
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KEY, value);
SharedPreferencesLoader.persist(editor);
}
@Override
public void onLoaderReset(Loader
}
}
تست و اجرای برنامه
LoaderManager متد onLoadFinished() را خودکار پس از هر تغییر پیکربندی فرا می خواند . برنامه را راه اندازی کرده و اطمینان حاصل کنید که مقدار ذخیره شده در preferences مشترک در هر مرحله از تغییر پیکربندی افزایش پیدا می کند .
11. تمرین : چرخه حیات activity و نخ ها
مثال ذیل توسط نخ تصویری از اینترنت دانلود کرده و تا زمانی که دانلود پایان می یابد یک کادر محاوره نمایش می دهد . در این نمونه سعی می کنیم نخ را حتی زمانی که activity مجدداً راه اندازی / restart می شود و همچنین محاوره به درستی نمایش داده و بسته می شود, حفظ و غیرفرار کنیم .
برای این منظور ابتدا پروژه ی جدیدی به نام de.vogella.android.threadslifecycle و activity به نام ThreadsLifecycleActivity ایجاد کنید . لازم است برای دسترسی داشتن به اینترنت مجوز مورد نیاز را در فایل AndroidManifest.xml تعریف کنید .
فایل AndroidManifest.xml ایجاد شده باید مشابه نمونه ی زیر باشد .
xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.vogella.android.threadslifecycle"
android:versioncode="1"
android:versionname="1.0">
<uses-sdk android:minsdkversion="10" />
<uses-permission android:name="android.permission.INTERNET">
uses-permission>
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".ThreadsLifecycleActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
application>
manifest>
فایل (layout) main.xml را به ترتیب زیر اصلاح کنید .
xml version="1.0" encoding="utf-8" ?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<linearlayout android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onclick="downloadPicture"
android:text="Click to start download">button>
<button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onclick="resetPicture"
android:text="Reset Picture">button>
linearlayout>
<imageview android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/icon">
imageview>
linearlayout>
حال activity را تنظیم کنید . در activity نام برده, نخ ذخیره شده و محاوره بسته می شود )حتی اگر خود activity نابود شود(
package de.vogella.android.threadslifecycle;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.ImageView;
public class ThreadsLifecycleActivity extends Activity {
// Static so that the thread access the latest attribute
private static ProgressDialog dialog;
private static Bitmap downloadBitmap;
private static Handler handler;
private ImageView imageView;
private Thread downloadThread;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// create a handler to update the UI
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
imageView.setImageBitmap(downloadBitmap);
dialog.dismiss();
}
};
// get the latest imageView after restart of the application
imageView = (ImageView) findViewById(R.id.imageView1);
Context context = imageView.getContext();
System.out.println(context);
// Did we already download the image?
if (downloadBitmap != null) {
imageView.setImageBitmap(downloadBitmap);
}
// check if the thread is already running
downloadThread = (Thread) getLastNonConfigurationInstance();
if (downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(this, "Download", "downloading");
}
}
public void resetPicture(View view) {
if (downloadBitmap != null) {
downloadBitmap = null;
}
imageView.setImageResource(R.drawable.icon);
}
public void downloadPicture(View view) {
dialog = ProgressDialog.show(this, "Download", "downloading");
downloadThread = new MyThread();
downloadThread.start();
}
// save the thread
@Override
public Object onRetainNonConfigurationInstance() {
return downloadThread;
}
// dismiss dialog if activity is destroyed
@Override
protected void onDestroy() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
super.onDestroy();
}
// Utiliy method to download image from the internet
static private Bitmap downloadBitmap(String url) throws IOException {
HttpUriRequest request = new HttpGet(url);
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
byte[] bytes = EntityUtils.toByteArray(entity);
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0,
bytes.length);
return bitmap;
} else {
throw new IOException("Download failed, HTTP response code "
+ statusCode + " - " + statusLine.getReasonPhrase());
}
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
// Simulate a slow network
try {
new Thread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
downloadBitmap = downloadBitmap("http://www.devoxx.com/download/attachments/4751369/DV11");
// Updates the user interface
handler.sendEmptyMessage(0);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
}
برنامه را اجرا کرده و روی دکمه ی مورد نظر کلیک کنید تا دانلود آغاز گردد . می توان درستی رفتار چرخه ی حیات را با تغییر نحوه و جهت نمایش صفحه در شبیه ساز با استفاده از میان بر Ctrl+F11 تست و آزمایش کرد .
توجه داشته باشید که Thread یک کلاس داخلی ایستا (static inner class) است . دقت داشته باشید که برای انجام پردازش (فرایند) پس زمینه ای حتماً از یک کلاس داخلی ایستا استفاده کنید زیرا در غیر این صورت کلاس داخلی حاوی یک ارجاع به کلاسی خواهد بود که کلاس داخلی در آن کلاس ایجاد شده . زمانی که thread به نمونه ی جدید از activity پاس داده می شود, وضعیت هدر رفت حافظه (memory leak) روی می دهد و آن به این خاطر است که نخ ذکر شده هنوز برای ارجاع دادن به activity قدیمی بکار گرفته می شود .
12. بهینه سازی استفاده از حافظه
استفاده از حافظه ی پنهان
شیوه ی دیگری که می توانید از آن طریق کارایی را بهبود ببخشید, ذخیره سازی موقت اشیإ سنگین و پر حجم است, به عنوان مثال در صورت دانلود تصاویر برای نمایش در ListView, باید آن ها را در حافظه ی پنهان نگه داشته تا از این طریق از دانلود مجدد آن ها در آینده خودداری کنید .
حافظه ی پنهان LRU (least recently used) حساب استفاده از تمامی اعضای خود را دارد . آیتم نام برده دارای یک ظرفیت معین است و در صورت پر شدن آن ظرفیت, حافظه ی مزبور آیتم هایی که طولانی مدت مورد استفاده قرار نگرفته اند را حذف می کند .
این رفتار در تصویر زیر به نمایش گذاشته شده است .
پلت فرم (محیط( اندروید, کلاس LruCache را از رابط برنامه سازی کاربردی 12 API)) به بعد (یا کتابخانه ی پشتیبانی نسخه ی 4) در اختیار برنامه نویس قرار می دهد . کلاس مذکور قابلیت پیاده سازی حافظه ی پنهان Least Recently Used را در اختیار برنامه نویس قرار می دهد . مثال :
public class ImageCache extends LruCache
super(maxSize);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
oldValue.recycle();
}
}
به منظور دستیابی به ظرفیت اولیه حافظه ی پنهان, می توان از MemoryClass بهره گرفت و بدین ترتین تنها از مقدار ناچیزی از کل حافظه ی در دسترس استفاده کرد . مثال زیر این نمونه را به زیبایی نمایش می دهد :
int memClass = ((ActivityManager)activity.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 8;
LruCache cache = new LruCache
استفاده از ساختار حافظه ی کارامد
سیستم اندروید ساختارهای موثری جهت نگاشت مقادیر به دیگر اشیإ فراهم می کند . در صورت امکان می توانید از این اشیإ استفاده کنید و در نتیجه از ایجاد (مجدد) اشیإ, درست مشابه نمونه ی HashMap, اجتناب کنید . همان طور که انتظار می رود, پروسه ی ایجاد اشیإ کمی سنگین بوده و با خودداری از رخداد مجدد آن می توان از تعداد دفعاتی که زباله روب باید فعال شده و مورد استفاده قرار گیرد کاست .
جدول ذیل مثال هایی از SparseArrays را ارائه می دهد .
جدول 1. ساختارهای کارامد حافظه
ساختار حافظه |
توضیح کاربرد |
SparseArray |
Maps اعداد صحیح (integer) را به اشیإ نگاشت می کند, از ایجاد اشیإ صحیح (integer) جلوگیری می کند . |
SparseBooleanArray |
اعداد صحیح را به بولی ها نگاشت می کند |
SparseIntArray |
integer ها را به integer های دیگر نگاشت می کند |
پاک سازی حافظه
از رابط برنامه سازی 14 (API) به بعد این امکان برای برنامه نویس فراهم شده که متد onTrimMemory () را بازنویسی کند (override) . این متد توسط سیستم اندروید زمانی صدا زده می شود که سیستم برای اجرای و پردازش فرایندهای پس زمینه نیاز به منابع کافی داشته باشد; متد مذکور به شما اطلاع می دهد که باید حافظه ی خود را پاک سازی کنید .
13. StrictMode
اندروید این قابلیت را برای برنامه نویس ایجاد می کند که به سیستم دستور دهد کلیه ی فرایندهای سنگین و طولانی که در نخ رابط کاربری در حال پردازش و اجرا است را گزارش دهد .
شما باید تا حد ممکن از اجرای فرایندهای طولانی در رابط UI خودداری کنید, که این شامل دسترسی به فایل و شبکه می شود .
برای جلوگیری از رخداد وضعیت مزبور, باید از StrictMode استفاده کنید . StrictMode از API 9 (ویرایش 2.3.3(اندروید قابل استفاده بوده و به شما اجازه می دهد رویه های نخ (thread policy) را ویژه ی برنامه ی خود تنظیم کنید.
از طریق StrictMode می توانید به سیستم اندروید دستور دهید برنامه ی شما را در صورت اجرای عملیات طولانی (crash) ناگهان متوقف کند (برای مثال می توان به input/output در نخ رابط کاربری اشاره کرد).
کد زیر نحوه ی استفاده از حالت فوق را تشریح می کند . به مجرد این که activity از تنظیمات مشخصه تخطی می کند, ناگهان توسط سیستم متوقف می شود.
package de.vogella.android.strictmode;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
public class TestStrictMode extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Activate StrictMode
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
// alternatively .detectAll() for all detectable problems
.penaltyLog()
.penaltyDeath()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
// alternatively .detectAll() for all detectable problems
.penaltyLog()
.penaltyDeath()
.build());
// Test code
setContentView(R.layout.main);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String eol = System.getProperty("line.separator");
try {
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(openFileOutput("myfile",
MODE_WORLD_WRITEABLE)));
writer.write("This is a test1." + eol);
writer.write("This is a test2." + eol);
writer.write("This is a test3." + eol);
writer.write("This is a test4." + eol);
writer.write("This is a test5." + eol);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
14. ارائه ی بازخورد از طریق محاوره
در صورت اجرای عملیات پیچیده و طولانی بهتر است از طریق ارائه ی بازخورد به کاربر درباره ی (در جریان بودن) عملیات مذکور اطلاع دهید.
برای مثال می توانید بازخوردی مبنی بر پیشرفت و جریان فرایند از طریق درایه ی action viewدر action bar در اختیار کاربر قرار دهید . متناوباً می توانید از یک progressbar در شِمای کلی (layout) خود بهره بگیرید . آیتم مذکور را روی visible تنظیم کرده و آن را حین عملیات طولانی مداوم بروز رسانی می کنید . از این رویکرد به این خاطر که رابط کاربری را واکنشی (responsive) می سازد با عنوان providing inline feedback (ارائه ی بازخورد درون برنامه ای) نیز یاد می شود.
جهت مسدود کردن رابط کاربری حین عملیات, می توان از محاوره ی progressbar بهره گرفت که تکامل و جریان فرایند را برای کاربر به نمایش در می آورد.