یه تابستون متفاوت با یه تصمیم هوشمندانه! دوره هوش مصنوعی یه تابستون متفاوت با یه تصمیم هوشمندانه! دوره هوش مصنوعی
🎯 ثبت نام

پردازش های پس زمینه در اندروید

این آموزش به شرح مفاهیم Thread ها، Handler ها و استفاده از کلاس AsyncTask می پردازد. همچنین مفهوم پردازش ناهمگام در اپلیکیشن های اندرویدی را تشریح می نماید. سپس نحوه ی مدیریت چرخه ی حیات/lifecycle با thread ها و پردازش پس زمینه ای را تحت پوشش قرار می دهد.
پروژه های این مبحث در محیط برنامه نویسی Android Studio نوشته و تست می شوند.

آموزش پردازش پس زمینه ای در اندروید

آموزش چرا استفاده از مفهوم همروندی/concurrency توصیه می شود؟

در حالت پیش فرض، یک اپلیکیشن اندرویدی در thread اصلی اجرا می شود. از اینرو تمامی دستورات به صورت پشت سرهم (خط به خط) و طبق یک ترتیب خاص اجرا می شوند. در چنین وضعیتی، اگر اپلیکیشن یک فرایند طولانی را راه اندازی کند، اپلیکیشن تا زمانی که عملیات مزبور به اتمام نرسیده، (thread اصلی مسدود شده) قادر به پردازش درخواست دیگری نخواهد بود.
به منظور ارائه ی تجربه ی کاربری بهینه، تمامی عملیات کند و طولانی می بایست در اپلیکیشن اندرویدی به صورت ناهمزمان (async) اجرا شوند. برای نیل به این هدف می توانید از ساختارهای مدیریت همروندی (concurrency) جاوا یا چارچوب نرم افزاری (framework) اندروید استفاده نمایید. از عملیات کند می توان به پردازش های مربوط به اینترنت، فایل و دسترسی به داده از دیتابیس و محاسبات پیچیده اشاره کرد.

نکته:

در اندروید چنانچه یک activity قادر نباشد با گذشت مدت 5 ثانیه از درخواست کاربر، پاسخ مناسب را ارائه دهد، سیستم بلافاصله یک پنجره محاوره ی حاوی پیغام Application not responding (ANR)را به نمایش می گذارد. در این پنجره کاربر می تواند با انتخاب گزینه ی مربوطه، اپلیکیشن را کاملا متوقف کند.

آموزش Thread اصلی یا UI thread

اندروید تمامی تسک ها و رخدادهای ورودی را در یک thread واحد به نام UI thread یا main thread مدیریت می نماید. در واقع سیستم اندروید تمامی رخدادها را در این thread، داخل یک صف جمع کرده و سپس event های قرار گرفته در این صف را با کمک نمونه ای از کلاس Looper پردازش می نماید. لازم به ذکر است که thread اصلی نمی تواند چندین عملیات را اداره کرده و در لحظه می تواند تنها یک رخداد ورودی را پردازش کند.

Thread اصلی

آموزش پردازش ناهمزمان و استفاده از thread ها در اندروید

اندروید برای پردازش async و ناهمزمان از کلاس Thread و جهت پردازش همزمان چند عملیات در پس زمینه پکیج java.util.concurrent را در اختیار توسعه دهنده قرار می دهد. این پکیج کلاس های ThreadPools و Executor را برای پردازش پس زمینه فراهم می نماید.
در صورت نیاز به آپدیت UI از یک Thread دیگر، لازم است با main thread همزمانی انجام دهید. به خاطر وجود این محدودیت ها، توسعه دهندگان اندروید اغلب از کدها و ساختارهای اختصاصی خود اندروید بهره می گیرند.
اندروید تعدادی ساختار ویژه جهت مدیریت همروندی (انجام چند پردازش در پس زمینه) ارائه می دهد که جدا از کدهای جاوا است.
از ساختارهای اختصاصی خود اندروید می توان از کلاس های android.os.Handler یا AsyncTasks نام برد. روش های پیچیده تری ویژه ی مدیریت همروندی وجود دارد که لازمه ی آن، استفاده از کلاس Loader، retained fragment ها و سرویس ها است.

توجه:

تا حد امکان از بکار بردن پنجره ی ProgressBar یا روش های مشابه که تعامل با UI را تا اتمام فرایند معینی مسدود می کند، خودداری نمایید. توصیه می شود برای این منظور یک توضیح مختصر UI همچنان قابلیت تعامل با کاربر را داشته باشد.

ارائه ی feedback و توضیح مختصر برای کاربر در طول اجرای عملیات طولانی

در طول اجرای عملیات طولانی، بهتر است توضیح مختصری در خصوص این عملیات برای کاربر به نمایش بگذارید.
می توانید این توضیحات را در قالب action bar یا action view در UI به نمایش بگذارید. روش دیگر این است که یک ProgressBar در layout تعریف کرده، آن را بر روی visible تنظیم نمایید و سپس در طول اجرای عملیات بروز رسانی کنید. هرچند توصیه می شود از روش اول استفاده نمایید چرا که در آن صورت ارتباط کاربر با UI قطع نمی شود و اپلیکیشن همچنان responsive می ماند.

آموزش کلاس Handler

آموزش استفاده از کلاس Handler جهت مدیریت همروندی

ابتدا جهت پیاده سازی مفهوم همروندی در موبایل و ایجاد یک thread دیگر سوای thread اصلی، می بایست یک آبجکت از کلاس Thread ایجاد نمایید. سپس می توانید با کمک کلاس های handler و Message خروجی thread پس زمینه را به thread اصلی اطلاع دهید.
آبجکت Handler خود را داخل thread ای که در آن ایجاد می شود، ثبت می نماید. این آبجکت یک پل ارتباطی ایجاد کرده و داده ها را از طریق آن به thread اصلی تحویل می دهد. به طور مثال، اگر شما یک نمونه ی جدید از Handler در متد onCreate() کلاس activity خود ایجاد نمایید، در آن صورت می توانید از آبجکت ذکر شده به راحتی داده های مورد نیاز را به thread اصلی ارسال نمایید. داده هایی که کلاس Handler ارسال می کند، می تواند آبجکتی از کلاس Message یا Runnable باشد.
Handler به ویژه در شرایطی کارامد تلقی می شود که لازم باشد داده هایی را چند بار به thread اصلی ارسال (post) نمایید.

آموزش ایجاد و استفاده ی مجدد از thread اصلی

به منظور پیاده سازی کلاس handler، می بایست یک کلاس فرزند (subclass) از آن ایجاد کرده و متد ()handleMessage آن را جهت پردازش پیغام ها بازنویسی (override) نمایید. سپس می توانید به راحتی با فراخوانی توابع sendMessage(Message) یا ()sendEmptyMessage پیغام های مورد نیاز را به آن ارسال کنید. جهت ارسال یک آبجکت Runnable به کلاس مذکور، لازم است متد ()post را بکار ببرید.
به منظور جلوگیری از ایجاد غیر ضروری آبجکت و اشغال حافظه، می توانید آبجکت Handler جاری کلاس activity خود را مجددا استفاده یا به اصطلاح بازیافت نمایید.

1
2
3
4
// Reuse existing handler if you don't
// have to override the message processing
handler = getWindow().getDecorView().getHandler();
<button></button>

کلاس View به شما این امکان را می دهد تا آبجکت هایی از جنس Runnable را به وسیله ی متد post() ارسال (post) نمایید (این متد به شما امکان می دهد تا پیغام پایان کار را به thread اصلی اعلان نمایید).

مثال:

کد زیر نحوه ی استفاده از یک handler از view را نمایش می دهد. فرض کنید activity شما از فایل layout زیر جهت تنظیم ظاهر خود استفاده می کند.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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></button>

با توجه به کد activity زیر که برای برنامه نوشته شده، زمانی که کاربر بر روی دکمه یا آبجکت Button کلیک می کند، آبجکت ProgressBar بلافاصله بروز رسانی می شود.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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();
                }
        }
}
<button></button>

آموزش کلاس AsyncTask

آموزش هدف استفاده از کلاس AsyncTask

کلاس AsyncTask به شما اجازه می دهد تا دستوراتی را در پس زمینه به اجرا بگذارید و سپس آن ها را با thread اصلی همگام نموده و همچنین گزارشاتی مربوط به تسک های در حال اجرا به thread اصلی تحویل دهید. لازم به ذکر است که از کلاس AsyncTasks اغلب برای اجرای عملیات در پس زمینه که UI را بروز رسانی می کنند، استفاده می شود.
در واقع کلاس AsyncTask یک کلاس انتزاعی و abstract است که به برنامه های اندرویدی امکان می دهد تا thread اصلی (UI) را به صورت بهینه مدیریت نمایید. AsyncTask به توسعه دهنده کمک می کند تا تسک هایی که چند ثانیه بیشتر طول نمی کشند را در پس زمینه به طور ناهمزمان اجرا نموده و نتیجه ی آن را در thread اصلی نمایش دهد.

آموزش استفاده ی کاربردی از کلاس AsyncTask

برای استفاده از کلاس AsyncTask لازم است از آن یک کلاس فرزند ایجاد نمایید (از AsyncTask یک کلاس مشتق نمایید). کلاس AsyncTask از generic و varargs استفاده می کند. پارامترهای ورودی عبارتند از: AsyncTask <typeofvarargparams, progressvalue, resultvalue>.
کلاس AsyncTask با صدا خوردن متد ()execute راه اندازی می شود. متد ()execute خود به تبع متدهای ()doInBackground و ()onPostExecute را فراخوانی می کند.
TypeOfVarArgParams به عنوان آرگومان ورودی به تابع ()doInBackground فرستاده می شود. ProgressValue ویژه ی اطلاعات پیشرفت فرایند بکار برده می شود و ResultValue باید به عنوان خروجی از متد ()doInBackground بازگردانی شود. این آرگومان خود به عنوان پارامتر ورودی به تابع ()onPostExecute پاس داده می شود.
متد ()doInBackground دربردارنده ی دستورات و کدهایی است که می بایست در یک thread مجزا در پس زمینه اجرا شوند.
متد ()onPostExecute خود را با Thread اصلی (UI) همگام ساخته و بروز رسانی اطلاعات آن را به دنبال دارد. زمانی که عملیات متد ()doInBackground به پایان می رسد، خود چارچوب نرم افزاری اندروید (FRAMEWORK) این متد را فراخوانی می کند.

آموزش اجرای همزمان چندین AsyncTasks

سیستم اندروید قبل از ویرایش 1.6 و بار دیگر از ورژن 3.0، تسک های مربوط به AsyncTask را به صورت پیش فرض به ترتیب (و پشت سرهم) اجرا می نماید. شما می توانید به سیستم اندروید اعلان نمایید که این تسک ها را به طور موازی با اجرای متد ()executeOnExecutor، در حالی که AsyncTask.THREAD_POOL_EXECUTOR به عنوان اولین پارامتر به آن ارسال شده، اجرا نماید.
تکه کد زیر این عملیات را نمایش می گذارد.

1
2
3
4
5
// ImageLoader extends AsyncTask
ImageLoader imageLoader = new ImageLoader( imageView );
// Execute in parallel
imageLoader.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, "http://url.com/image.png" );
<button></button>
نکته:

AsyncTask تغییرات در نحوه ی پیکربندی و تنظیمات را به صورت خودکار مدیریت نمی کند، بدین معنی که چنانچه activity از نو ساخته شود، برنامه نویس باید آن را با نوشتن کد مربوطه مدیریت نماید. یک راه حل پرطرفدار و مورد استفاده ی اغلب توسعه دهندگان این است که کلاس AsyncTask را در یک retained headless fragment (fragment های فاقد UI که اطلاعات خود را بین تغییرات در نحوه ی پیکربندی حفظ می کنند) تعریف نمایید.

مثال: پیاده سازی AsyncTask

کد زیر چگونگی استفاده از کلاس AsyncTask جهت دانلود محتوای یک صفحه ی وب را به نمایش می گذارد.
یک پروژه ی جدید اندروید به نام de.vogella.android.asynctask و یک activity به نام ReadWebpageAsyncTask ایجاد نمایید. مجوز android.permission.INTERNET را در فایل تنظیمات اپلیکیشن (manifest) تنظیم نمایید.
Layout زیر را ایجاد نمایید.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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>
<button></button>

بدنه ی activity خود را به صورت زیر ویرایش نمایید.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package de.vogella.android.asynctask;
// imports cut out for brevity
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>string, void, string> {
                @Override
                protected String doInBackground(String... urls) {
                        // we use the OkHttp library from https://github.com/square/okhttp
                        OkHttpClient client = new OkHttpClient();
                        Request request =
                                        new Request.Builder()
                                        .url(urls[0])
                                        .build();
                                 Response response = client.newCall(request).execute();
                                 if (response.isSuccessful()) {
                                         return response.body().string();
                                 }
                        }
                        return "Download failed";
                }
                @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/index.html" });
        }
}
<button></button>

اپلیکیشن خود را اجرا نموده و دکمه ی مربوطه را کلیک نمایید. اطلاعات صفحه وب موردنظر در پس زمینه خوانده می شود. به مجرد اتمام این فرایند، می بینید که آبجکت TextView با اطلاعات جدید بروز آوری می شود.

آموزش پردازش پس زمینه ای و مدیریت چرخه ی حیات (lifecycle)

آموزش حفظ اطلاعات مربوط به وضعیت (state) بین تغییراتی که در نحوه ی پیکربندی رخ می دهد

یکی از مشکلات و چالش های استفاده از thread، در نظر گرفتن و مدیریت چرخه ی حیات اپلیکیشن می باشد. سیستم اندروید ممکن است activity را به کلی از بین برده، از حافظه پاک کند یا با اعمال تغییری در نحوه ی پیکربندی سبب شود که activity مجددا ایجاد گردد.
بعلاوه توسعه دهنده می بایست مدیریت محاوره های (dialog) باز را در نظر بگیرد چرا که این محاوره ها در نهایت به activity میزبان وصل هستند. در صورتی که activity مجددا راه اندازی شده و شما قصد دسترسی به محاوره ی جاری را بکنید، خواهید دید که یک خطای زمان اجرا View not attached to window manager از سمت سیستم صادر می شود.
به منظور ذخیره ی یک آبجکت، شما می توانید متد ()onRetainNonConfigurationInstance را مورد استفاده قرار دهید. در واقع چنانچه قرار است activity مورد نظر از بین رفته و مجددا راه اندازی شود، شما می توانید با فراخوانی متد مزبور اطلاعات نمونه ی جاری را در یک آبجکت نگه داشته و دوباره با ساخت activity، داده های ذخیره شده در آبجکت را به آن تحویل دهید. برای بازیابی این آبجکت می توانید متد ()getLastNonConfigurationInstance را فراخوانی کنید. از این طریق شما قادر خواهید بود، حتی زمانی که activity جاری از بین رفته و مجددا راه اندازی می شود، آبجکت حامل اطلاعات activity (برای مثال یک thread در حال اجرا) را نگه داشته و محتوای آن را به activity از نو ساخته شده پاس دهید.
در صورتی که activity برای اولین بار راه اندازی شده و یا با صدا خورده شدن متد ()finish خاتمه می یابد، متد مذکور در خروجی مقدار null را برخواهد گرداند.
البته لازم به توضیح است که متد ()onRetainNonConfigurationInstance از ورژن 13 کتابخانه های اندروید (API 13) تقریبا منسوخ تلقی می شود. بهتر است برای بدست آوردن نتیجه ی مورد نظر از fragment ها استفاده نموده و متد ()setRetainInstance را جهت نگه داشتن اطلاعات بین تغییرات در نحوه ی پیکربندی بکار ببرید.

آموزش استفاده از آبجکت application جهت ذخیره اطلاعات چندین آبجکت

چنانچه لازم است چندین آبجکت را جهت نگهداری اطلاعات activity ها و بازگردانی آن ها پس از تغییرات در نحوه ی پیکربندی بکار ببرید، در آن صورت می توانید کلاس Application که اطلاعات مربوط به وضعیت سراسری اپلیکیشن را در خود ذخیره نگه می دارد را در برنامه ی خود پیاده سازی نمایید.
برای نیل به این هدف، کافی است مقدار classname را به خصیصه (attribute) android:name در فایل تنظیمات اپلیکیشن خود تخصیص دهید.

1
2
3
4
5
6
7
8
9
10
11
<application android:icon="@drawable/icon" android:label="@string/app_name"
                                 android:name="MyApplicationClass">
                    <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>
<button></button>

لایه ی runtime اندروید کلاس application را به صورت خودکار ایجاد کرده و تا زمانی که کل فرایند اپلیکیشن متوقف نشده، این آبجکت همچنان در دسترس باقی خواهند ماند.
آبجکت اپلیکیشن می تواند اطلاعات مربوط به وضعیت سراسری اپلیکیشن را نگه دارد. این آبجکت قبل از اینکه فرایند مربوط به اپلیکیشن یا پکیج ایجاد شود، نمونه سازی شده و در دسترس قرار می گیرد.
از کلاس نام برده می توان جهت نگهداری و بازگردانی اطلاعات آبجکت هایی که در چندین activity هستند یا برای چرخه ی حیات کلی اپلیکیشن قابل دسترسی می باشد، استفاده نمایید. داخل بدنه ی متد ()onCreate می توانید آبجکت هایی ایجاد کرده و آن ها را از طریق فیلدهای public یا توابع getter در دسترس قرار دهید.
متد ()onTerminate در کلاس اپلیکیشن فقط و فقط به منظور تست بکار می رود. همان طور که از اسمش پیدا است، این متد فرایند تخصیص یافته به اپلیکیشن را به طور کلی از بین می برد. بنابراین چنانچه این متد فراخوانی شده و فرایند میزبان خاتمه یابد، در آن صورت تمامی منابع اختصاص یافته به اپلیکیشن به صورت خودکار آزاد می شوند و اطلاعات از حافظه ی دستگاه حذف می گردد.
جهت دسترسی به آبجکت Application می توانید متد ()getApplication را در سطح activity فراخوانی نمایید.

آموزش Fragment ها و پردازش موازی در پس زمینه (background processing)

آموزش نگهداری اطلاعات آبجکت جهت بازگردانی آن پس از تغییر در نحوه ی پیکربندی

می توانید از fragment های بدون UI استفاده نموده و اطلاعات آن ها را با فراخوانی متد ()setRetainInstance ذخیره نگه دارید و پس از تغییر در تنظیمات اپلیکیشن آن ها را بازگردانی کنید.
از این طریق آبجکت Thread یا AsyncTask با تغییر در تنظیمات و نحوه ی پیکربندی اپلیکیشن در حافظه نگه داشته می شود و شما می توانید بدون اینکه نگران مدیریت چرخه ی حیات اپلیکیشن خود بوده و آن را به صورت صریح درنظر بگیرید، عملیاتی را در پس زمنیه و در موازات هم به اجرا بگذارید.

آموزش Fragment های فاقد UI (headless fragments)

اگر پردازش هایی را در پس زمینه به اجرا گذاشته اید، در آن صورت می توانید در زمان اجرا (به صورت dynamic) یک headless fragment به اپلیکیشن خود متصل کرده و setRetainInstance() را بر روی true تنظیم نمایید. به دنبال این کار، fragment (اطلاعات آبجکت) با تغییر در نحوه ی پیکربندی حفظ شده و شما می توانید پردازش ناهمزمان در آن اجرا کنید.

آموزش کلاس Loader

آموزش کاربرد کلاس Loader

کلاس Loader به شما این امکان را می دهد تا داده های مورد نیاز را به صورت ناهمزمان (async) در activity یا fragment مربوطه بارگذاری نمایید. این کلاس می تواند منبع داده ها را رصد کرده (زیر نظر داشته) و زمانی که محتویات تغییر کردند، نتایج جدید را تحویل دهد. همچنین این امکان را فراهم می کند تا داده ها با تغییر در نحوه ی پیکربندی ذخیره شده و در زمان لازم (پس از ساخته شدن fragment) بازگردانی شوند.
داده ها را می توان به وسیله ی Loader در حافظه ی نهان نگه داشت (cache کرد) و زمانی که تغییری در نحوه ی پیکربندی رخ می دهد، داده های ذخیره شده را بازگردانی نمود. کلاس های Loader از اندروید 3.0 معرفی شده و بخشی از compatibility layer (جهت پشتیبانی از نسخه های قدیمی) برای ورژن 1.6 به بعد اندروید محسوب می شود.

آموزش پیاده سازی کلاس Loader

می توانید از کلاس AsyncTaskLoader به عنوان یک پایه برای پیاده سازی های Loader خود استفاده نمایید. LoaderManager یک activity یا fragment می تواند چندین نمونه از Loader را مدیریت کند. نحوه ی فراخوانی Loader در زیر به نمایش گذاشته شده است.

1
2
3
# start a new loader or re-connect to existing one
getLoaderManager().initLoader(0, null, this);
<button></button>

اولین پارامتر یک ID یا شناسه ی منحصربفرد است که کلاس callback برای شناسایی Loader مورد استفاده قرار می دهد. دومین پارامتر ارسالی یک آبجکت bundle است که برای اطلاعات بیشتر به متد پاس داده می شود. سومین پارامتر ورودی متد ()initLoader، کلاسی است که به محض شروع مقداردهی اولیه (کلاسcallback ) فراخوانی می شود. این کلاس می بایست اینترفیس LoaderManager.LoaderCallbacks را پیاده سازی کند. در واقع مرسوم است activity یا fragment ای که از Loader استفاده می کند، همراه با آن اینترفیس LoaderManager.LoaderCallbacks را نیز پیاده سازی نماید.
Loader به طور مستقیم با فراخوانی متد ()getLoaderManager.()initLoader ایجاد نمی شود، بلکه کلاس callback یا بازفراخوان آن را در بدنه ی تابع ()onCreateLoader خود ایجاد می کند.
هنگامی که Loader خواندن داده ها به صورت ناهمزمان را به اتمام می رساند، متد ()onLoadFinished از کلاس callback صدا خورده می شود. در اینجا (داخل بدنه ی این متد) شما می تواند UI و ظاهر اپلیکیشن خود را با داده های جدید بروز آوری نمایید.

آموزش دیتابیس SQLite و پیاده سازی کلاس CursorLoader

چارچوب نرم افزاری اندروید (framework) یک کلاس به نام CursorLoader در اختیار توسعه دهنده قرار می دهد که پیاده سازی Loader را به صورت پیش فرض دربرداشته و اتصال به دیتابیس (database connection) SQLite را خود مدیریت می نماید.
برای کوئری گرفتن و درخواست داده از یک ContentProvider که مبتنی بر دیتابیس SQLite می باشد، برنامه نویسان اغلب از کلاس CursorLoader استفاده می کنند. این کلاس Loader در پس زمینه (background thread) از دیتابیس کوئری می گیرد، به همین جهت تعامل اپلیکیشن با کاربر مختل نشده و در نتیجه UI مسدود نمی شود.
کلاس CursorLoader جایگزین آبجکت های Activity-managed cursor که در ویرایش های قبلی اندروید بکار می رفتند، محسوب می شود.
در صورتی که Cursor نامعتبر شود، متد ()onLoadReset در کلاس callback فراخوانی می گردد.

تمرین: پیاده سازی Loader اختصاصی برای مدیریت و بارگذاری ناهمگام جفت های کلید-مقدار (preferences)

پیاده سازی کلاس

در تمرین زیر یک کلاس loader اختصاصی برای بارگذاری و مدیریت جفت های کلید-مقدار (preferences) پیاده سازی خواهد کرد. با هر بار بارگذاری (load)، مقدار preference افزایش می یابد.
یک پروژه ی جدید به نام com.Vogella.android.loader.preferences و همچنین یک activity جدید به نام MainActivity ایجاد نمایید.
با تعریف کلاس زیر که یک پیاده سازی اختصاصی از AsyncTaskLoader هست شما در واقع جفت های کلید-مقدار (shared preferences) را به صورت ناهمگام مدیریت می نمایید (shared preferences برای ذخیره ی کلید/مقدارهای کوچک بکار می رود تا برای مثال با بسته شدن اپلیکیشن یا خاموش شدن دستگاه اطلاعات مربوطه همچون تنظیمات اپلیکیشن از دست نرود).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
                        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<sharedpreferences>
                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();
                }
        }
}
<button></button>

نمونه کد زیر استفاده از این loader را در کلاس activity به نمایش می گذارد.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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<sharedpreferences> {
        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<sharedpreferences> onCreateLoader(int id, Bundle args) {
                return (new SharedPreferencesLoader(this));
        }
        @SuppressLint("CommitPrefEdits")
        @Override
        public void onLoadFinished(Loader<sharedpreferences> 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<sharedpreferences> loader) {
                // NOT used
        }
}
<button></button>

آموزش تست اپلیکیشن

پس از هر بار تغییر در نحوه ی پیکربندی/تنظیمات اپلیکیشن، کلاس LoaderManager متد ()onLoadFinished را به صورت خودکار در activity صدا می زند. اپلیکیشن را اجرا نموده و مطمئن شوید که با هر بار تغییر در نحوه ی پیکربندی (config change) مقدار ذخیره شده در shared preferences افزایش می یابد.

آموزش استفاده از service ها

می توانید از سرویس های اندروید برای اجرای عملیات و تسک های پس زمینه ای بهره بگیرید. برای این منظور می توانید به مبحث آموزش سرویس ها در اندروید مراجعه نمایید.

تمرین: چرخه ی حیات activity و thread ها

نمونه برنامه ی زیر یک فایل تصویری را در thread پس زمینه از اینترنت دانلود کرده و یک پنجره ی محاوره به نمایش می گذارد که تا اتمام پروسه ی دانلود در UI باقی می ماند. برنامه را طوری خواهید نوشت که با از بین رفتن و ساخت مجدد activity نیز thread حفظ شده و پنجره ی محاوره به درستی نمایش داده/بسته شود.
برای این مثال یک پروژه و activity جدید به ترتیب به نام های de.vogella.android.threadslifecycle و ThreadsLifecycleActivity ایجاد نمایید. همچنین لازم است مجوز استفاده از اینترنت را نیز برای اپلیکیشن در فایل manifest تنظیم نمایید.
محتویات فایل تنظیمات اپلیکیشن (AndroidManifest.xml) شما می بایست ظاهری مشابه زیر داشته باشد.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8" ?>
          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>
<button></button>

محتوای فایل main.xml را به صورت زیر ویرایش نمایید.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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>
<button></button>

حال بدنه ی کلاس activity را ویرایش نمایید. داخل این کلاس thread ذخیره شده و محاوره به محض از بین رفتن activity، بسته می شود.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
                        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 {
                        }
                }
        }
}
<button></button>

اپلیکیشن خود را اجرا نموده و جهت راه اندازی فرایند دانلود، بر روی دکمه ی start کلیک نمایید. برای اینکه مطمئن شوید رفتار اپلیکیشن در خصوص مدیریت lifecycle یا چرخه ی حیات صحیح می باشد، می توانید در محیط شبیه ساز با فشردن کلید های ctrl+F11 جهت و وضعیت (orientation) نمایش را تغییر دهید.

لازم به ذکر است که Thread یک کلاس static و inner (ایستا و تعریف شده در دل یک کلاس دیگر) می باشد. به طور کلی لازم است برای پردازش های پس زمینه ای یک کلاس static inner را بکار ببرید چرا که در غیر این صورت کلاس inner (درون ساخته در دل کلاس دیگر) اشاره گری (reference) به کلاسی که در آن تعریف شده را در خود نگه می دارد. زمانی که thread به نمونه ی جدید از activity ارسال می شود، از آنجایی که این thread همچنان به نمونه ی قبلی activity اشاره دارد، هدر رفت حافظه و memory leak رخ خواهد داد.

1395/12/08 6403 1996
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

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