یه تابستون متفاوت با یه تصمیم هوشمندانه! دوره هوش مصنوعی با تخفیف ویژه، فقط با کد AI84 دوره هوش مصنوعی با تخفیف ویژه، فقط با کد AI84
🎯 ثبت نام

پردازش پس زمینه ای با 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 را پردازش می کند .

صف پیغام در اندروید به همراه 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 {
       
@Override
       
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
       
implements
SharedPreferences.OnSharedPreferenceChangeListener {
   
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 static final String KEY = "prefs"
;
   
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 onCreateLoader(int
id, Bundle args) {
       
return (new SharedPreferencesLoader(this
));
    }

    @SuppressLint(
"CommitPrefEdits"
)
   
@Override
   
public void
onLoadFinished(Loader loader,
                               SharedPreferences prefs) {
       
int value = prefs.getInt(KEY, 0
);
        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 loader) {
       
// NOT used
   
}
}

 

تست و اجرای برنامه

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) حساب استفاده از تمامی اعضای خود را دارد . آیتم نام برده دارای یک ظرفیت معین است و در صورت پر شدن آن ظرفیت, حافظه ی مزبور آیتم هایی که طولانی مدت مورد استفاده قرار نگرفته اند را حذف می کند .

این رفتار در تصویر زیر به نمایش گذاشته شده است .

شکل کلی LRU cache

پلت فرم (محیط( اندروید, کلاس LruCache را از رابط برنامه سازی کاربردی 12 API)) به بعد (یا کتابخانه ی پشتیبانی نسخه ی 4) در اختیار برنامه نویس قرار می دهد . کلاس مذکور قابلیت پیاده سازی حافظه ی پنهان Least Recently Used را در اختیار برنامه نویس قرار می دهد . مثال :

public class ImageCache extends LruCache {

   
public ImageCache(int
maxSize) {
       
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(cacheSize
);

 

استفاده از ساختار حافظه ی کارامد

سیستم اندروید ساختارهای موثری جهت نگاشت مقادیر به دیگر اشیإ فراهم می کند . در صورت امکان می توانید از این اشیإ استفاده کنید و در نتیجه از ایجاد (مجدد) اشیإ, درست مشابه نمونه ی 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 بهره گرفت که تکامل و جریان فرایند را برای کاربر به نمایش در می آورد.

1394/07/27 10629 3161
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

نظرات خود را ثبت کنید...