প্রক্রিয়া এবং থ্রেড ওভারভিউ

যখন কোনো অ্যাপ্লিকেশন কম্পোনেন্ট চালু হয় এবং অ্যাপ্লিকেশনটিতে অন্য কোনো কম্পোনেন্ট চালু না থাকে, তখন অ্যান্ড্রয়েড সিস্টেম অ্যাপ্লিকেশনটির জন্য একটি একক এক্সিকিউশন থ্রেডসহ একটি নতুন লিনাক্স প্রসেস শুরু করে। ডিফল্টরূপে, একই অ্যাপ্লিকেশনের সমস্ত কম্পোনেন্ট একই প্রসেস এবং থ্রেডে চলে, যাকে মেইন থ্রেড বলা হয়।

যদি কোনো অ্যাপ্লিকেশন কম্পোনেন্ট চালু হয় এবং সেই অ্যাপ্লিকেশনের জন্য আগে থেকেই একটি প্রসেস চালু থাকে, কারণ অ্যাপ্লিকেশনটির অন্য কোনো কম্পোনেন্ট ইতিমধ্যেই চালু হয়ে গেছে, তাহলে কম্পোনেন্টটি সেই প্রসেসের মধ্যেই চালু হয় এবং একই এক্সিকিউশন থ্রেড ব্যবহার করে। তবে, আপনি আপনার অ্যাপ্লিকেশনের বিভিন্ন কম্পোনেন্টকে আলাদা আলাদা প্রসেসে চালানোর ব্যবস্থা করতে পারেন এবং যেকোনো প্রসেসের জন্য অতিরিক্ত থ্রেড তৈরি করতে পারেন।

এই ডকুমেন্টে একটি অ্যান্ড্রয়েড অ্যাপ্লিকেশনে প্রসেস এবং থ্রেড কীভাবে কাজ করে তা আলোচনা করা হয়েছে।

প্রক্রিয়া

ডিফল্টরূপে, একটি অ্যাপ্লিকেশনের সমস্ত উপাদান একই প্রসেসে চলে এবং বেশিরভাগ অ্যাপ্লিকেশন এটি পরিবর্তন করে না। তবে, যদি আপনার কোনো নির্দিষ্ট উপাদান কোন প্রসেসের অধীনে থাকবে তা নিয়ন্ত্রণ করার প্রয়োজন হয়, তাহলে আপনি ম্যানিফেস্ট ফাইলে তা করতে পারেন।

প্রতিটি ধরণের কম্পোনেন্ট এলিমেন্টের— <activity> , <service> , <receiver> , এবং <provider> —ম্যানিফেস্ট এন্ট্রিতে একটি android:process অ্যাট্রিবিউট থাকে, যা কম্পোনেন্টটি কোন প্রসেসে চলবে তা নির্দিষ্ট করে দেয়। আপনি এই অ্যাট্রিবিউটটি এমনভাবে সেট করতে পারেন যাতে প্রতিটি কম্পোনেন্ট তার নিজস্ব প্রসেসে চলে, অথবা এমনভাবেও সেট করতে পারেন যাতে কিছু কম্পোনেন্ট একটি প্রসেস শেয়ার করে এবং অন্যগুলো না করে।

আপনি android:process সেট করতে পারেন, যাতে বিভিন্ন অ্যাপ্লিকেশনের উপাদানগুলো একই প্রসেসে চলে; তবে শর্ত হলো, অ্যাপ্লিকেশনগুলোর লিনাক্স ইউজার আইডি একই হতে হবে এবং সেগুলো একই সার্টিফিকেট দ্বারা স্বাক্ষরিত হতে হবে।

<application> এলিমেন্টটি android:process অ্যাট্রিবিউটও সাপোর্ট করে, যা ব্যবহার করে আপনি একটি ডিফল্ট ভ্যালু সেট করতে পারেন যা সমস্ত কম্পোনেন্টের জন্য প্রযোজ্য হবে।

যখন ব্যবহারকারীকে আরও জরুরি পরিষেবা প্রদানকারী অন্যান্য প্রসেসের জন্য রিসোর্সের প্রয়োজন হয়, তখন অ্যান্ড্রয়েড কোনো এক সময়ে একটি প্রসেস বন্ধ করে দেওয়ার সিদ্ধান্ত নিতে পারে। ফলস্বরূপ, বন্ধ হয়ে যাওয়া প্রসেসটিতে চলমান অ্যাপ্লিকেশন কম্পোনেন্টগুলো ধ্বংস হয়ে যায়। যখন সেই কম্পোনেন্টগুলোর করার মতো কাজ থাকে, তখন সেগুলোর জন্য আবার একটি প্রসেস চালু করা হয়।

কোন প্রসেসগুলো বন্ধ করা হবে, সেই সিদ্ধান্ত নেওয়ার সময় অ্যান্ড্রয়েড সিস্টেম ব্যবহারকারীর কাছে সেগুলোর আপেক্ষিক গুরুত্ব বিবেচনা করে। উদাহরণস্বরূপ, স্ক্রিনে আর দেখা যায় না এমন কার্যকলাপ পরিচালনাকারী একটি প্রসেসের তুলনায়, এটি দৃশ্যমান কার্যকলাপ পরিচালনাকারী একটি প্রসেসকে আরও সহজে বন্ধ করে দেয়। সুতরাং, কোনো প্রসেস বন্ধ করার সিদ্ধান্তটি সেই প্রসেসে চলমান উপাদানগুলোর অবস্থার উপর নির্ভর করে।

প্রসেস লাইফসাইকেলের বিশদ বিবরণ এবং অ্যাপ্লিকেশন স্টেটগুলির সাথে এর সম্পর্ক "প্রসেস এবং অ্যাপ লাইফসাইকেল" অংশে আলোচনা করা হয়েছে।

থ্রেড

যখন কোনো অ্যাপ্লিকেশন চালু করা হয়, তখন সিস্টেম অ্যাপ্লিকেশনটির জন্য একটি এক্সিকিউশন থ্রেড তৈরি করে, যাকে মেইন থ্রেড বলা হয়। এই থ্রেডটি খুবই গুরুত্বপূর্ণ, কারণ এটি ড্রয়িং ইভেন্টসহ উপযুক্ত ইউজার ইন্টারফেস উইজেটগুলিতে ইভেন্ট প্রেরণের দায়িত্বে থাকে। এছাড়াও, প্রায় সবসময় এই থ্রেডেই আপনার অ্যাপ্লিকেশনটি অ্যান্ড্রয়েড UI টুলকিটের android.widget এবং android.view প্যাকেজের কম্পোনেন্টগুলোর সাথে ইন্টারঅ্যাক্ট করে। এই কারণে, মেইন থ্রেডকে কখনও কখনও UI থ্রেডও বলা হয়। তবে, বিশেষ পরিস্থিতিতে, একটি অ্যাপের মেইন থ্রেড তার UI থ্রেড নাও হতে পারে। আরও তথ্যের জন্য, থ্রেড অ্যানোটেশন দেখুন।

সিস্টেমটি কোনো কম্পোনেন্টের প্রতিটি ইনস্ট্যান্সের জন্য আলাদা থ্রেড তৈরি করে না । একই প্রসেসে চলমান সমস্ত কম্পোনেন্ট UI থ্রেডে ইনস্ট্যানশিয়েট করা হয় এবং প্রতিটি কম্পোনেন্টের সিস্টেম কলগুলো সেই থ্রেড থেকেই ডিসপ্যাচ করা হয়। ফলস্বরূপ, যে মেথডগুলো সিস্টেম কলব্যাকে সাড়া দেয়—যেমন ব্যবহারকারীর কার্যকলাপ জানানোর জন্য onKeyDown() বা কোনো লাইফসাইকেল কলব্যাক মেথড—সেগুলো সবসময় প্রসেসের UI থ্রেডেই চলে।

উদাহরণস্বরূপ, যখন ব্যবহারকারী স্ক্রিনের কোনো বোতামে স্পর্শ করেন, তখন আপনার অ্যাপের UI থ্রেড উইজেটটিতে টাচ ইভেন্টটি প্রেরণ করে, যা ফলস্বরূপ তার 'প্রেসড' অবস্থা সেট করে এবং ইভেন্ট কিউতে একটি 'ইনভ্যালিডেট' অনুরোধ পোস্ট করে। UI থ্রেড অনুরোধটি কিউ থেকে বের করে আনে এবং উইজেটটিকে নিজেকে পুনরায় আঁকতে অবহিত করে।

আপনার অ্যাপ্লিকেশনটি সঠিকভাবে প্রয়োগ না করলে, ব্যবহারকারীর ইন্টারঅ্যাকশনের প্রতিক্রিয়ায় যখন অ্যাপটি নিবিড় কাজ করে, তখন এই সিঙ্গেল-থ্রেড মডেলটি দুর্বল পারফরম্যান্স দিতে পারে। UI থ্রেডে দীর্ঘ অপারেশন, যেমন নেটওয়ার্ক অ্যাক্সেস বা ডাটাবেস কোয়েরি, পুরো UI-কে ব্লক করে দেয়। যখন থ্রেডটি ব্লক থাকে, তখন ড্রয়িং ইভেন্ট সহ কোনো ইভেন্টই ডিসপ্যাচ করা যায় না।

ব্যবহারকারীর দৃষ্টিকোণ থেকে, অ্যাপ্লিকেশনটি আটকে গেছে বলে মনে হয়। আরও খারাপ ব্যাপার হলো, যদি UI থ্রেডটি কয়েক সেকেন্ডের বেশি সময় ধরে ব্লক থাকে, তবে ব্যবহারকারীর সামনে " অ্যাপ্লিকেশন সাড়া দিচ্ছে না " (ANR) ডায়ালগ বক্সটি প্রদর্শিত হয়। তখন ব্যবহারকারী আপনার অ্যাপ্লিকেশনটি বন্ধ করে দেওয়ার বা এমনকি আনইনস্টল করে দেওয়ার সিদ্ধান্ত নিতে পারেন।

মনে রাখবেন যে অ্যান্ড্রয়েড UI টুলকিট থ্রেড-সেফ নয় । তাই, ওয়ার্কার থ্রেড থেকে আপনার UI পরিবর্তন করবেন না। আপনার ইউজার ইন্টারফেসের সমস্ত পরিবর্তন UI থ্রেড থেকে করুন। অ্যান্ড্রয়েডের সিঙ্গেল-থ্রেড মডেলের দুটি নিয়ম রয়েছে:

  1. UI থ্রেডটি ব্লক করবেন না।
  2. UI থ্রেডের বাইরে থেকে অ্যান্ড্রয়েড UI টুলকিট অ্যাক্সেস করবেন না।

কর্মী থ্রেড

এই সিঙ্গেল-থ্রেড মডেলের কারণে, আপনার অ্যাপ্লিকেশনের UI-এর রেসপন্সিভনেসের জন্য UI থ্রেডকে ব্লক না করাটা অত্যন্ত জরুরি। যদি আপনার এমন কোনো অপারেশন করার থাকে যা তাৎক্ষণিক নয়, তবে সেগুলো আলাদা ব্যাকগ্রাউন্ড বা ওয়ার্কার থ্রেডে সম্পন্ন করুন। শুধু মনে রাখবেন যে, আপনি UI বা মেইন থ্রেড ছাড়া অন্য কোনো থ্রেড থেকে UI আপডেট করতে পারবেন না।

এই নিয়মগুলো অনুসরণ করতে আপনাকে সাহায্য করার জন্য, অ্যান্ড্রয়েড অন্যান্য থ্রেড থেকে UI থ্রেড অ্যাক্সেস করার বিভিন্ন উপায় প্রদান করে। এখানে এমন কিছু পদ্ধতির তালিকা দেওয়া হলো যা সাহায্য করতে পারে:

নিম্নলিখিত উদাহরণগুলি দেখায় কিভাবে একটি টাস্ককে ব্যাকগ্রাউন্ড থ্রেডে অফলোড করতে হয় এবং টাস্কটি সম্পন্ন হলে UI থ্রেড আপডেট করতে হয়:

কোটলিন

// 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));
    });
}

এই বাস্তবায়নটি থ্রেড-সেফ, কারণ ব্যাকগ্রাউন্ড অপারেশনটি একটি পৃথক থ্রেড থেকে করা হয়, অন্যদিকে ImageView সর্বদা UI থ্রেড থেকে পরিচালনা করা হয়।

তবে, অপারেশনের জটিলতা বাড়ার সাথে সাথে, এই ধরনের কোড জটিল এবং রক্ষণাবেক্ষণ করা কঠিন হয়ে উঠতে পারে। একটি ওয়ার্কার থ্রেডের সাথে আরও জটিল ইন্টারঅ্যাকশন পরিচালনা করার জন্য, আপনি UI থ্রেড থেকে পাঠানো মেসেজ প্রসেস করতে আপনার ওয়ার্কার থ্রেডে একটি Handler ব্যবহার করার কথা ভাবতে পারেন। ব্যাকগ্রাউন্ড থ্রেডে কীভাবে কাজ শিডিউল করতে হয় এবং UI থ্রেডে তা জানাতে হয়, তার সম্পূর্ণ ব্যাখ্যার জন্য ‘ব্যাকগ্রাউন্ড ওয়ার্ক ওভারভিউ’ দেখুন।

থ্রেড-সেফ পদ্ধতি

কিছু ক্ষেত্রে, আপনার প্রয়োগ করা মেথডগুলো একাধিক থ্রেড থেকে কল করা হয়, এবং তাই সেগুলোকে অবশ্যই থ্রেড-সেফ করে লিখতে হবে।

এটি মূলত সেইসব মেথডের ক্ষেত্রে প্রযোজ্য যেগুলোকে দূর থেকে কল করা যায়, যেমন একটি বাউন্ড সার্ভিসের মেথড। যখন একটি IBinder এ ইমপ্লিমেন্ট করা কোনো মেথডকে সেই একই প্রসেস থেকে কল করা হয় যেখানে IBinder চলছে, তখন মেথডটি কলারের থ্রেডে এক্সিকিউট হয়। তবে, যখন কলটি অন্য কোনো প্রসেস থেকে করা হয়, তখন মেথডটি এমন একটি থ্রেডে এক্সিকিউট হয় যা সিস্টেম দ্বারা IBinder এর একই প্রসেসে রক্ষণাবেক্ষণ করা থ্রেডের পুল থেকে বেছে নেওয়া হয়। এটি প্রসেসটির UI থ্রেডে এক্সিকিউট হয় না।

উদাহরণস্বরূপ, যেখানে একটি সার্ভিসের onBind() মেথডটি সেই সার্ভিসের প্রসেসের UI থ্রেড থেকে কল করা হয়, onBind() যে অবজেক্টটি রিটার্ন করে, তার মধ্যে ইমপ্লিমেন্ট করা মেথডগুলো, যেমন রিমোট প্রসিডিউর কল (RPC) মেথড ইমপ্লিমেন্টকারী কোনো সাবক্লাস, পুলের থ্রেডগুলো থেকে কল করা হয়। যেহেতু একটি সার্ভিসের একাধিক ক্লায়েন্ট থাকতে পারে, তাই একাধিক পুল থ্রেড একই সময়ে একই IBinder মেথড ব্যবহার করতে পারে, সুতরাং IBinder মেথডগুলোকে অবশ্যই থ্রেড-সেফ হিসেবে ইমপ্লিমেন্ট করতে হবে।

একইভাবে, একটি কন্টেন্ট প্রোভাইডার অন্যান্য প্রসেস থেকে আসা ডেটা রিকোয়েস্ট গ্রহণ করতে পারে। ContentResolver এবং ContentProvider ক্লাসগুলো ইন্টারপ্রসেস কমিউনিকেশন (IPC) কীভাবে পরিচালিত হয় তার বিস্তারিত বিবরণ গোপন রাখে, কিন্তু ContentProvider যে মেথডগুলো সেই রিকোয়েস্টগুলোর উত্তর দেয়—যেমন query() , insert() , delete() , update() , এবং getType() —সেগুলোকে কন্টেন্ট প্রোভাইডারের প্রসেসের নিজস্ব থ্রেড পুল থেকে কল করা হয়, প্রসেসটির UI থ্রেড থেকে নয়। যেহেতু এই মেথডগুলো একই সময়ে যেকোনো সংখ্যক থ্রেড থেকে কল করা হতে পারে, তাই এগুলোকেও থ্রেড-সেফ হিসেবে ইমপ্লিমেন্ট করতে হবে।

আন্তঃপ্রক্রিয়া যোগাযোগ

অ্যান্ড্রয়েড RPC ব্যবহার করে IPC-এর জন্য একটি ব্যবস্থা প্রদান করে, যেখানে কোনো অ্যাক্টিভিটি বা অ্যাপ্লিকেশনের অন্য কোনো কম্পোনেন্ট একটি মেথডকে কল করে, কিন্তু সেটি দূরবর্তীভাবে অন্য একটি প্রসেসে এক্সিকিউট করা হয় এবং এর ফলাফল কলকারীকে ফেরত পাঠানো হয়। এর জন্য একটি মেথড কল এবং তার ডেটাকে এমন একটি স্তরে ভেঙে ফেলতে হয় যা অপারেটিং সিস্টেম বুঝতে পারে, সেটিকে লোকাল প্রসেস ও অ্যাড্রেস স্পেস থেকে রিমোট প্রসেস ও অ্যাড্রেস স্পেসে প্রেরণ করতে হয়, এবং তারপর সেখানে কলটিকে পুনরায় একত্রিত করে ও পুনরায় সম্পাদন করতে হয়।

এরপর রিটার্ন ভ্যালুগুলো বিপরীত দিকে প্রেরণ করা হয়। অ্যান্ড্রয়েড এই IPC ট্রানজ্যাকশনগুলো সম্পাদনের জন্য প্রয়োজনীয় সমস্ত কোড সরবরাহ করে, ফলে আপনি RPC প্রোগ্রামিং ইন্টারফেস সংজ্ঞায়িত ও বাস্তবায়নের উপর মনোযোগ দিতে পারেন।

IPC সম্পাদন করার জন্য, আপনার অ্যাপ্লিকেশনকে অবশ্যই bindService() ব্যবহার করে একটি সার্ভিসের সাথে বাইন্ড করতে হবে। আরও তথ্যের জন্য, সার্ভিসেস ওভারভিউ দেখুন।