بررسی اجمالی فرآیندها و موضوعات

وقتی یک کامپوننت برنامه شروع به کار می‌کند و برنامه هیچ کامپوننت دیگری در حال اجرا ندارد، سیستم اندروید یک فرآیند لینوکس جدید برای برنامه با یک نخ اجرا آغاز می‌کند. به طور پیش‌فرض، تمام اجزای یک برنامه در یک فرآیند و نخ اجرا می‌شوند که نخ اصلی نامیده می‌شود.

اگر یک کامپوننت برنامه شروع به کار کند و از قبل یک فرآیند برای آن برنامه وجود داشته باشد، زیرا کامپوننت دیگری از برنامه قبلاً شروع شده است، آنگاه کامپوننت در داخل آن فرآیند شروع به کار می‌کند و از همان نخ اجرا استفاده می‌کند. با این حال، می‌توانید ترتیب دهید که کامپوننت‌های مختلف در برنامه شما در فرآیندهای جداگانه اجرا شوند و می‌توانید برای هر فرآیند، نخ‌های اضافی ایجاد کنید.

این سند نحوه عملکرد فرآیندها و نخ‌ها در یک برنامه اندروید را مورد بحث قرار می‌دهد.

فرآیندها

به طور پیش‌فرض، تمام اجزای یک برنامه در یک فرآیند اجرا می‌شوند و اکثر برنامه‌ها این را تغییر نمی‌دهند. با این حال، اگر متوجه شدید که باید کنترل کنید که یک جزء خاص به کدام فرآیند تعلق دارد، می‌توانید این کار را در فایل مانیفست انجام دهید.

ورودی مانیفست برای هر نوع عنصر کامپوننت - <activity> ، <service> ، <receiver> و <provider> - از یک ویژگی android:process پشتیبانی می‌کند که می‌تواند فرآیندی را که کامپوننت در آن اجرا می‌شود مشخص کند. می‌توانید این ویژگی را طوری تنظیم کنید که هر کامپوننت در فرآیند خودش اجرا شود یا اینکه برخی از کامپوننت‌ها یک فرآیند را به اشتراک بگذارند در حالی که برخی دیگر این کار را نکنند.

شما همچنین می‌توانید android:process طوری تنظیم کنید که اجزای برنامه‌های مختلف در یک فرآیند اجرا شوند، مشروط بر اینکه برنامه‌ها شناسه کاربری لینوکس یکسانی داشته باشند و با گواهی‌های یکسان امضا شده باشند.

عنصر <application> همچنین از ویژگی android:process پشتیبانی می‌کند که می‌توانید از آن برای تنظیم یک مقدار پیش‌فرض که برای همه کامپوننت‌ها اعمال می‌شود، استفاده کنید.

اندروید ممکن است در مقطعی تصمیم به خاموش کردن یک فرآیند بگیرد، زمانی که منابع توسط فرآیندهای دیگری که مستقیماً به کاربر خدمت می‌کنند مورد نیاز باشند. در نتیجه، اجزای برنامه که در فرآیند خاموش شده اجرا می‌شوند، از بین می‌روند. زمانی که کاری برای انجام دادن برای آن اجزا وجود داشته باشد، فرآیند دوباره برای آنها آغاز می‌شود.

سیستم اندروید هنگام تصمیم‌گیری در مورد اینکه کدام فرآیندها را خاموش کند، اهمیت نسبی آنها را برای کاربر می‌سنجد. برای مثال، فرآیندی را که میزبان فعالیت‌هایی است که دیگر روی صفحه قابل مشاهده نیستند، در مقایسه با فرآیندی که میزبان فعالیت‌های قابل مشاهده است، با سهولت بیشتری خاموش می‌کند. بنابراین، تصمیم در مورد خاتمه دادن به یک فرآیند، به وضعیت اجزای در حال اجرا در آن فرآیند بستگی دارد.

جزئیات چرخه حیات فرآیند و ارتباط آن با وضعیت‌های برنامه در بخش فرآیندها و چرخه حیات برنامه مورد بحث قرار گرفته است.

موضوعات

وقتی یک برنامه اجرا می‌شود، سیستم یک نخ اجرا برای برنامه ایجاد می‌کند که نخ اصلی نامیده می‌شود. این نخ بسیار مهم است، زیرا مسئول ارسال رویدادها به ویجت‌های رابط کاربری مناسب، از جمله رویدادهای طراحی، است. همچنین تقریباً همیشه نخی است که در آن برنامه شما با اجزای بسته‌های android.widget و android.view از جعبه ابزار رابط کاربری اندروید تعامل دارد. به همین دلیل، نخ اصلی گاهی اوقات نخ رابط کاربری نامیده می‌شود. با این حال، در شرایط خاص، نخ اصلی یک برنامه ممکن است نخ رابط کاربری آن نباشد. برای اطلاعات بیشتر، به حاشیه‌نویسی‌های نخ مراجعه کنید.

سیستم برای هر نمونه از یک کامپوننت، یک نخ جداگانه ایجاد نمی‌کند . تمام کامپوننت‌هایی که در یک فرآیند مشابه اجرا می‌شوند، در نخ UI نمونه‌سازی می‌شوند و فراخوانی‌های سیستم به هر کامپوننت از آن نخ ارسال می‌شوند. در نتیجه، متدهایی که به فراخوانی‌های سیستم پاسخ می‌دهند - مانند onKeyDown() برای گزارش اقدامات کاربر، یا یک متد فراخوانی چرخه عمر - همیشه در نخ UI فرآیند اجرا می‌شوند.

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

مگر اینکه برنامه خود را به درستی پیاده‌سازی کنید، این مدل تک‌رشته‌ای می‌تواند عملکرد ضعیفی را در زمانی که برنامه شما در پاسخ به تعامل کاربر، کار فشرده‌ای انجام می‌دهد، به همراه داشته باشد. انجام عملیات طولانی در نخ رابط کاربری، مانند دسترسی به شبکه یا پرس‌وجوهای پایگاه داده، کل رابط کاربری را مسدود می‌کند. وقتی نخ مسدود می‌شود، هیچ رویدادی نمی‌تواند ارسال شود، از جمله رویدادهای ترسیم.

از دید کاربر، به نظر می‌رسد که برنامه دیگر پاسخ نمی‌دهد. حتی بدتر از آن، اگر رابط کاربری برای بیش از چند ثانیه مسدود شود، کاربر با پیام « برنامه پاسخ نمی‌دهد » (ANR) مواجه می‌شود. در این صورت، کاربر ممکن است تصمیم به ترک برنامه یا حتی حذف نصب آن بگیرد.

به خاطر داشته باشید که جعبه ابزار رابط کاربری اندروید از نظر thread-safe نیست . بنابراین، رابط کاربری خود را از طریق worker thread دستکاری نکنید. تمام دستکاری‌های رابط کاربری خود را از طریق UI thread انجام دهید. دو قانون برای مدل تک-thread اندروید وجود دارد:

  1. نخ رابط کاربری (UI thread) را مسدود نکنید.
  2. از خارج از نخ رابط کاربری به جعبه ابزار رابط کاربری اندروید دسترسی پیدا نکنید.

موضوعات کارگر

به دلیل این مدل تک‌رشته‌ای، برای پاسخگویی رابط کاربری برنامه شما بسیار مهم است که نخ رابط کاربری را مسدود نکنید. اگر عملیاتی برای انجام دارید که آنی نیستند، حتماً آنها را در نخ‌های پس‌زمینه یا کارگر جداگانه انجام دهید. فقط به یاد داشته باشید که نمی‌توانید رابط کاربری را از هیچ نخی غیر از نخ رابط کاربری یا نخ اصلی به‌روزرسانی کنید.

برای کمک به شما در رعایت این قوانین، اندروید چندین روش برای دسترسی به نخ رابط کاربری از نخ‌های دیگر ارائه می‌دهد. در اینجا لیستی از روش‌هایی که می‌توانند به شما کمک کنند، آورده شده است:

مثال‌های زیر نحوه‌ی انتقال یک وظیفه به یک نخ پس‌زمینه و به‌روزرسانی نخ رابط کاربری پس از اتمام وظیفه را نشان می‌دهند:

کاتلین

// Kotlin coroutines implementation.
fun onClick(v: View) {
    // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment).
    lifecycleScope.launch {
        // Run the blocking task on the IO dispatcher.
        val bitmap = withContext(Dispatchers.IO) {
            BitmapFactory.decodeFile("image.png")
        }
        // Back on the main thread, update the UI.
        imageView.setImageBitmap(bitmap)
    }
}

جاوا

// Java Executor implementation.
// (executorService is assumed to be defined elsewhere).
public void onClick(View v) {
    executorService.execute(() -> {
        // Run the heavy task on a background thread.
        Bitmap bitmap = BitmapFactory.decodeFile("image.png");

        // Update the View on the UI thread.
        imageView.post(() -> imageView.setImageBitmap(bitmap));
    });
}

این پیاده‌سازی از نوع thread-safe است، زیرا عملیات پس‌زمینه از یک thread جداگانه انجام می‌شود در حالی که ImageView همیشه از thread رابط کاربری دستکاری می‌شود.

با این حال، با افزایش پیچیدگی عملیات، این نوع کد می‌تواند پیچیده و نگهداری آن دشوار شود. برای مدیریت تعاملات پیچیده‌تر با یک نخ کارگر، می‌توانید استفاده از یک Handler در نخ کارگر خود را برای پردازش پیام‌های ارسالی از نخ UI در نظر بگیرید. برای توضیح کامل نحوه زمان‌بندی کار روی نخ‌های پس‌زمینه و ارتباط با نخ UI، به بخش «مرور کلی کار پس‌زمینه» مراجعه کنید.

روش‌های ایمن برای نخ

در برخی شرایط، متدهایی که پیاده‌سازی می‌کنید از بیش از یک نخ فراخوانی می‌شوند و بنابراین باید طوری نوشته شوند که در برابر نخ ایمن باشند.

این امر در درجه اول برای متدهایی که می‌توانند از راه دور فراخوانی شوند، مانند متدهای موجود در یک سرویس متصل ، صادق است. هنگامی که فراخوانی یک متد پیاده‌سازی شده در یک IBinder در همان فرآیندی که IBinder در حال اجرا است، آغاز می‌شود، متد در نخ فراخواننده اجرا می‌شود. با این حال، هنگامی که فراخوانی در فرآیند دیگری آغاز می‌شود، متد در نخی اجرا می‌شود که از مجموعه‌ای از نخ‌هایی که سیستم در همان فرآیند IBinder نگهداری می‌کند، انتخاب شده است. این متد در نخ رابط کاربری فرآیند اجرا نمی‌شود.

برای مثال، در حالی که متد onBind() یک سرویس از نخ رابط کاربری (UI thread) فرآیند سرویس فراخوانی می‌شود، متدهایی که در شیء‌ای که onBind() برمی‌گرداند پیاده‌سازی می‌شوند، مانند یک زیرکلاس که متدهای فراخوانی از راه دور (RPC) را پیاده‌سازی می‌کند، از نخ‌های موجود در مخزن فراخوانی می‌شوند. از آنجا که یک سرویس می‌تواند بیش از یک کلاینت داشته باشد، بیش از یک نخ مخزن می‌توانند همزمان با همان متد IBinder درگیر شوند، بنابراین متدهای IBinder باید به گونه‌ای پیاده‌سازی شوند که thread-safe باشند.

به طور مشابه، یک ارائه دهنده محتوا می‌تواند درخواست‌های داده‌ای را که از فرآیندهای دیگر سرچشمه می‌گیرند، دریافت کند. کلاس‌های ContentResolver و ContentProvider جزئیات نحوه مدیریت ارتباط بین فرآیندی (IPC) را پنهان می‌کنند، اما متدهای ContentProvider که به آن درخواست‌ها پاسخ می‌دهند - متدهای query() ، insert() ، delete() ، update() و getType() - از مجموعه‌ای از نخ‌ها در فرآیند ارائه دهنده محتوا فراخوانی می‌شوند، نه از نخ رابط کاربری فرآیند. از آنجا که این متدها ممکن است همزمان از هر تعداد نخ فراخوانی شوند، آنها نیز باید به گونه‌ای پیاده‌سازی شوند که در برابر نخ ایمن باشند.

ارتباط بین فرآیندی

اندروید مکانیزمی برای IPC با استفاده از RPCها ارائه می‌دهد که در آن یک متد توسط یک فعالیت یا مؤلفه دیگر برنامه فراخوانی می‌شود اما از راه دور در یک فرآیند دیگر اجرا می‌شود و هر نتیجه‌ای به فراخواننده بازگردانده می‌شود. این امر مستلزم تجزیه فراخوانی متد و داده‌های آن به سطحی است که سیستم عامل می‌تواند آن را درک کند، آن را از فضای فرآیند و آدرس محلی به فضای فرآیند و آدرس راه دور منتقل می‌کند و سپس فراخوانی را در آنجا دوباره اسمبل و اجرا می‌کند.

سپس مقادیر برگشتی در جهت مخالف ارسال می‌شوند. اندروید تمام کد لازم برای انجام این تراکنش‌های IPC را ارائه می‌دهد، بنابراین می‌توانید روی تعریف و پیاده‌سازی رابط برنامه‌نویسی RPC تمرکز کنید.

برای انجام IPC، برنامه شما باید با استفاده از bindService() به یک سرویس متصل شود. برای اطلاعات بیشتر، به مرور کلی سرویس‌ها مراجعه کنید.