এএনআর (ভিউ)

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

যখন কোনো অ্যান্ড্রয়েড অ্যাপের UI থ্রেড খুব বেশি সময় ধরে ব্লক হয়ে থাকে, তখন একটি "অ্যাপ্লিকেশন সাড়া দিচ্ছে না" (ANR) ত্রুটি দেখা দেয়। অ্যাপটি যদি ফোরগ্রাউন্ডে থাকে, তবে সিস্টেম ব্যবহারকারীকে একটি ডায়ালগ বক্স দেখায়, যেমনটি চিত্র ১-এ দেখানো হয়েছে। এই ANR ডায়ালগ বক্সটি ব্যবহারকারীকে অ্যাপটি জোর করে বন্ধ করার সুযোগ দেয়।

ব্যবহারকারীকে ANR ডায়ালগ বক্স দেখানো হয়েছে।
চিত্র ১. ব্যবহারকারীকে প্রদর্শিত ANR ডায়ালগ

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

নিম্নলিখিত শর্তগুলির মধ্যে কোনো একটি ঘটলে আপনার অ্যাপের জন্য একটি ANR ট্রিগার হয়:

  • ইনপুট প্রেরণে সময়সীমা অতিক্রান্ত : যদি আপনার অ্যাপ ৫ সেকেন্ডের মধ্যে কোনো ইনপুট ইভেন্টে (যেমন কী-প্রেস বা স্ক্রিন টাচ) সাড়া না দেয়।
  • সার্ভিস সম্পাদন : যদি আপনার অ্যাপ দ্বারা ঘোষিত কোনো সার্ভিস কয়েক সেকেন্ডের মধ্যে Service.onCreate() এবং Service.onStartCommand()/Service.onBind() সম্পাদন শেষ করতে না পারে।
  • **Service.startForeground() কল করা হয়নি**: যদি আপনার অ্যাপ ফোরগ্রাউন্ডে একটি নতুন সার্ভিস চালু করার জন্য Context.startForegroundService() ব্যবহার করে, কিন্তু সার্ভিসটি ৫ সেকেন্ডের মধ্যে startForeground() কল না করে।
  • ইনটেন্টের ব্রডকাস্ট : যদি একটি BroadcastReceiver একটি নির্দিষ্ট সময়ের মধ্যে তার কার্য সম্পাদন শেষ না করে। যদি অ্যাপটি ফোরগ্রাউন্ডে কোনো কার্যকলাপ চালু থাকে, তবে এই টাইমআউটটি ৫ সেকেন্ড।
  • **JobScheduler ইন্টারঅ্যাকশন: যদি কোনো JobService কয়েক সেকেন্ডের মধ্যে JobService.onStartJob() বা JobService.onStopJob() থেকে রিটার্ন না করে, অথবা যদি ব্যবহারকারীর শুরু করা কোনো জব চালু হয় এবং JobService.setNotification() JobService.onStartJob() কল না করে। অ্যান্ড্রয়েড ১৩ এবং তার নিচের সংস্করণের অ্যাপগুলোর জন্য, ANR-গুলো সাইলেন্ট থাকে এবং অ্যাপে রিপোর্ট করা হয় না। অ্যান্ড্রয়েড ১৪ এবং তার উপরের সংস্করণের অ্যাপগুলোর জন্য, ANR-গুলো এক্সপ্লিসিট থাকে এবং অ্যাপে রিপোর্ট করা হয়।

আপনার অ্যাপে যদি ANR দেখা দেয়, তবে সমস্যাটি নির্ণয় ও সমাধান করতে আপনি এই নিবন্ধের নির্দেশনা ব্যবহার করতে পারেন।

সমস্যাগুলো সমাধান করুন

সমস্যাটি শনাক্ত করার পর, সাধারণভাবে দেখা যায় এমন সমস্যাগুলো সমাধান করতে আপনি এই বিভাগের পরামর্শগুলো ব্যবহার করতে পারেন।

প্রধান থ্রেডে ধীরগতির কোড

আপনার কোডের সেই জায়গাগুলো চিহ্নিত করুন যেখানে অ্যাপের প্রধান থ্রেড ৫ সেকেন্ডের বেশি সময় ধরে ব্যস্ত থাকে। আপনার অ্যাপে সন্দেহজনক ব্যবহারের ক্ষেত্রগুলো সন্ধান করুন এবং ANR-টি পুনরায় তৈরি করার চেষ্টা করুন।

উদাহরণস্বরূপ, চিত্র ২-এ একটি ট্রেসভিউ টাইমলাইন দেখানো হয়েছে যেখানে প্রধান থ্রেডটি ৫ সেকেন্ডের বেশি সময় ধরে ব্যস্ত রয়েছে।

চিত্র ২. ট্রেসভিউ টাইমলাইন যা একটি ব্যস্ত প্রধান থ্রেড দেখাচ্ছে

চিত্র ২। ট্রেসভিউ টাইমলাইন, যেখানে একটি ব্যস্ত প্রধান থ্রেড দেখানো হচ্ছে।

চিত্র ২ থেকে দেখা যায় যে, বেশিরভাগ ত্রুটিপূর্ণ কোড onClick(View) হ্যান্ডলারে ঘটে, যেমনটি নিম্নলিখিত কোড উদাহরণে দেখানো হয়েছে:

কোটলিন

override fun onClick(v: View) {
    // This task runs on the main thread.
    BubbleSort.sort(data)
}

জাভা

@Override
public void onClick(View view) {
    // This task runs on the main thread.
    BubbleSort.sort(data);
}

এক্ষেত্রে, মেইন থ্রেডে চলমান কাজটি একটি ওয়ার্কার থ্রেডে স্থানান্তর করা উচিত। অ্যান্ড্রয়েড ফ্রেমওয়ার্কে এমন কিছু ক্লাস রয়েছে যা কাজটি ওয়ার্কার থ্রেডে স্থানান্তর করতে সাহায্য করে। আরও তথ্যের জন্য ওয়ার্কার থ্রেডস দেখুন।

প্রধান থ্রেডে I/O

মেইন থ্রেডে I/O অপারেশন চালানো হলো মেইন থ্রেডে ধীরগতির অপারেশনের একটি সাধারণ কারণ, যা ANR ঘটাতে পারে। Compose-এ, ডেভেলপাররা প্রায়শই প্রাথমিক অবস্থা নির্ধারণ করার চেষ্টা করার সময় ভুলবশত ডিস্ক রিড (যেমন SharedPreferences বা ডাটাবেস কল) চালু করে ফেলেন।

দীর্ঘ সময় ধরে চলা I/O অপারেশনগুলো UI লেয়ার থেকে দূরে সম্পাদন করুন। একটি ViewModelwithContext(Dispatchers.IO) ব্যবহার করুন অথবা, আরও ভালো হয়, ডেটা লেয়ারে একটি Repository ব্যবহার করুন। পূর্ববর্তী বিভাগে যেমন দেখানো হয়েছে, সমস্ত IO অপারেশন একটি ওয়ার্কার থ্রেডে সরিয়ে নেওয়ার পরামর্শ দেওয়া হয়।

IO অপারেশনের কিছু উদাহরণ হলো নেটওয়ার্ক এবং স্টোরেজ অপারেশন। আরও তথ্যের জন্য, ‘নেটওয়ার্ক অপারেশন সম্পাদন’ এবং ‘ডেটা সংরক্ষণ’ দেখুন।

লক বিরোধ

কিছু ক্ষেত্রে, যে কাজের জন্য ANR ঘটে, তা অ্যাপের প্রধান থ্রেডে সরাসরি সম্পাদিত হয় না। যদি কোনো ওয়ার্কার থ্রেড এমন কোনো রিসোর্সের ওপর লক ধরে রাখে যা প্রধান থ্রেডের কাজ সম্পন্ন করার জন্য প্রয়োজন, তাহলে একটি ANR ঘটতে পারে।

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

চিত্র ৩. ট্রেসভিউ টাইমলাইন যা একজন ওয়ার্কার থ্রেডে সম্পাদিত কাজ দেখাচ্ছে

চিত্র ৩। ট্রেসভিউ টাইমলাইন যা একটি ওয়ার্কার থ্রেডে সম্পাদিত কাজ প্রদর্শন করে।

কিন্তু আপনার ব্যবহারকারীরা যদি এখনও ANR-এর সম্মুখীন হন, তাহলে আপনার অ্যান্ড্রয়েড ডিভাইস মনিটরে প্রধান থ্রেডের অবস্থা দেখা উচিত। সাধারণত, প্রধান থ্রেডটি RUNNABLE অবস্থায় থাকে, যদি এটি UI আপডেট করার জন্য প্রস্তুত থাকে এবং সাধারণভাবে সাড়া দেয়।

কিন্তু যদি প্রধান থ্রেডটি তার কার্যক্রম পুনরায় শুরু করতে না পারে, তাহলে এটি BLOCKED অবস্থায় থাকে এবং ইভেন্টগুলিতে সাড়া দিতে পারে না। এই অবস্থাটি অ্যান্ড্রয়েড ডিভাইস মনিটরে Monitor বা Wait হিসেবে প্রদর্শিত হয়, যেমনটি চিত্র ৫-এ দেখানো হয়েছে।

চিত্র ৪. মনিটরের প্রধান থ্রেডের অবস্থা

চিত্র ৪। মনিটরের স্ট্যাটাসে প্রধান থ্রেড

নিম্নলিখিত ট্রেসটি একটি অ্যাপের প্রধান থ্রেড দেখাচ্ছে যা একটি রিসোর্সের জন্য অপেক্ষা করতে গিয়ে ব্লক হয়ে আছে:

...
AsyncTask #2" prio=5 tid=18 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
  | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
  | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
  | stack=0x94a7e000-0x94a80000 stackSize=1038KB
  | held mutexes= "mutator lock"(shared held)
  at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
  - locked <0x083105ee> (a java.lang.Boolean)
  at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
  at android.os.AsyncTask$2.call(AsyncTask.java:305)
  at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
  at java.lang.Thread.run(Thread.java:761)
...

ট্রেসটি পর্যালোচনা করলে আপনি সেই কোডটি খুঁজে পেতে পারেন যা মেইন থ্রেডকে ব্লক করে রেখেছে। পূর্ববর্তী ট্রেসে, নিম্নলিখিত কোডটি সেই লকটি ধরে রাখার জন্য দায়ী যা মেইন থ্রেডকে ব্লক করে রেখেছিল:

কোটলিন

override fun onClick(v: View) {
    // The worker thread holds a lock on lockedResource
    LockTask().execute(data)

    synchronized(lockedResource) {
        // The main thread requires lockedResource here
        // but it has to wait until LockTask finishes using it.
    }
}

class LockTask : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? =
            synchronized(lockedResource) {
                // This is a long-running operation, which makes
                // the lock last for a long time
                BubbleSort.sort(params[0])
            }
}

জাভা

@Override
public void onClick(View v) {
    // The worker thread holds a lock on lockedResource
  new LockTask().execute(data);

  synchronized (lockedResource) {
      // The main thread requires lockedResource here
      // but it has to wait until LockTask finishes using it.
  }
}

public class LockTask extends AsyncTask<Integer[], Integer, Long> {
  @Override
  protected Long doInBackground(Integer[]... params) {
      synchronized (lockedResource) {
          // This is a long-running operation, which makes
          // the lock last for a long time
          BubbleSort.sort(params[0]);
      }
  }
}

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

কোটলিন

fun onClick(v: View) {
    val lock = java.lang.Object()
    val waitTask = WaitTask(lock)
    synchronized(lock) {
        try {
            waitTask.execute(data)
            // Wait for this worker thread's notification
            lock.wait()
        } catch (e: InterruptedException) {
        }
    }
}

internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        synchronized(lock) {
            BubbleSort.sort(params[0])
            // Finished, notify the main thread
            lock.notify()
        }
    }
}

জাভা

public void onClick(View v) {
  WaitTask waitTask = new WaitTask();
  synchronized (waitTask) {
      try {
          waitTask.execute(data);
          // Wait for this worker thread’s notification
          waitTask.wait();
      } catch (InterruptedException e) {}
  }
}

class WaitTask extends AsyncTask<Integer[], Integer, Long> {
  @Override
  protected Long doInBackground(Integer[]... params) {
      synchronized (this) {
          BubbleSort.sort(params[0]);
          // Finished, notify the main thread
          notify();
      }
  }
}

আরও কিছু পরিস্থিতি আছে যা প্রধান থ্রেডকে ব্লক করতে পারে, যার মধ্যে রয়েছে Lock (Lock) ও Semaphore ) ব্যবহারকারী থ্রেড, সেইসাথে রিসোর্স পুল (যেমন ডাটাবেস সংযোগের একটি পুল) বা অন্যান্য মিউচুয়াল এক্সক্লুশন (mutex) প্রক্রিয়া।

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

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

ধীরগতির সম্প্রচার রিসিভার

অ্যাপগুলো ব্রডকাস্ট রিসিভারের মাধ্যমে ব্রডকাস্ট মেসেজে সাড়া দিতে পারে, যেমন এয়ারপ্লেন মোড চালু বা বন্ধ করা কিংবা কানেক্টিভিটি স্ট্যাটাসের পরিবর্তন। যখন কোনো অ্যাপ ব্রডকাস্ট মেসেজটি প্রসেস করতে অতিরিক্ত সময় নেয়, তখন একটি ANR ঘটে।

নিম্নলিখিত ক্ষেত্রে ANR ঘটে থাকে:

  • একটি ব্রডকাস্ট রিসিভার যথেষ্ট পরিমাণ সময়ের মধ্যে তার onReceive মেথডটির সম্পাদন সম্পন্ন করতে পারেনি।
  • একটি ব্রডকাস্ট রিসিভার goAsync কল করে এবং PendingResult অবজেক্টের উপর finish কল করতে ব্যর্থ হয়।

আপনার অ্যাপের BroadcastReceiver এর onReceive মেথডে শুধুমাত্র ছোটখাটো অপারেশন করা উচিত। তবে, যদি কোনো ব্রডকাস্ট মেসেজের ফলে আপনার অ্যাপের আরও জটিল প্রক্রিয়াকরণের প্রয়োজন হয়, তাহলে কাজটি একটি ViewModel এ (Kotlin coroutines, scopes, এবং dispatchers-এর শক্তি ব্যবহার করে) স্থানান্তর করা উচিত, যদি কাজটি সম্পন্ন হতে সর্বোচ্চ কয়েক সেকেন্ড সময় লাগার সম্ভাবনা থাকে; অথবা যেকোনো ধরনের স্টেট হোল্ডারে; কিংবা কয়েক সেকেন্ডের বেশি সময় লাগার সম্ভাবনা থাকলে WorkManager এ স্থানান্তর করা যেতে পারে।

আপনার ব্রডকাস্ট রিসিভার অ্যাপের প্রধান থ্রেডে দীর্ঘ সময় ধরে চলা কোনো অপারেশন সম্পাদন করে কিনা, তা শনাক্ত করতে আপনি Traceview-এর মতো টুল ব্যবহার করতে পারেন। উদাহরণস্বরূপ, চিত্র ৬-এ এমন একটি ব্রডকাস্ট রিসিভারের টাইমলাইন দেখানো হয়েছে, যেটি প্রধান থ্রেডে প্রায় ১০০ সেকেন্ড ধরে একটি মেসেজ প্রসেস করে।

চিত্র ৫. ট্রেসভিউ টাইমলাইন যা প্রধান থ্রেডে `BroadcastReceiver`-এর কাজ দেখাচ্ছে।

চিত্র ৫। ট্রেসভিউ টাইমলাইন, যা প্রধান থ্রেডে BroadcastReceiver কার্যকলাপ দেখাচ্ছে।

এই আচরণটি BroadcastReceiver এর onReceive() মেথডে দীর্ঘ সময় ধরে চলা অপারেশন সম্পাদন করার কারণে হতে পারে, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

কোটলিন

override fun onReceive(context: Context, intent: Intent) {
    // This is a long-running operation
    BubbleSort.sort(data)
}

জাভা

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

এই ধরনের পরিস্থিতিতে, দীর্ঘ সময় ধরে চলা অপারেশনটিকে একটি IntentService এ সরিয়ে নেওয়ার পরামর্শ দেওয়া হয়, কারণ এটি তার কাজ সম্পাদনের জন্য একটি ওয়ার্কার থ্রেড ব্যবহার করে। নিচের কোডটিতে দেখানো হয়েছে কীভাবে একটি দীর্ঘ সময় ধরে চলা অপারেশন প্রসেস করার জন্য IntentService ব্যবহার করতে হয়:

কোটলিন

override fun onReceive(context: Context, intent: Intent) {
    Intent(context, MyIntentService::class.java).also { intentService ->
        // The task now runs on a worker thread.
        context.startService(intentService)
    }
}

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(intent: Intent?) {
        BubbleSort.sort(data)
    }
}

জাভা

@Override
public void onReceive(Context context, Intent intent) {
    // The task now runs on a worker thread.
    Intent intentService = new Intent(context, MyIntentService.class);
    context.startService(intentService);
}

public class MyIntentService extends IntentService {
  @Override
  protected void onHandleIntent(@Nullable Intent intent) {
      BubbleSort.sort(data);
  }
}

IntentService ব্যবহারের ফলে, দীর্ঘ সময় ধরে চলা অপারেশনটি মেইন থ্রেডের পরিবর্তে একটি ওয়ার্কার থ্রেডে সম্পাদিত হয়। চিত্র ৭-এ Traceview টাইমলাইনে ওয়ার্কার থ্রেডে স্থানান্তরিত কাজটি দেখানো হয়েছে।

চিত্র ৬. একটি ওয়ার্কার থ্রেডে প্রক্রিয়াকৃত ব্রডকাস্ট মেসেজ দেখানো ট্রেসভিউ টাইমলাইন।

চিত্র ৬। একটি ওয়ার্কার থ্রেডে প্রক্রিয়াকৃত ব্রডকাস্ট বার্তাটি দেখানো ট্রেসভিউ টাইমলাইন।

আপনার ব্রডকাস্ট রিসিভার মেসেজটি প্রসেস করার জন্য আরও সময় প্রয়োজন, এই সংকেত দিতে goAsync() ব্যবহার করতে পারে। তবে, আপনার PendingResult অবজেক্টে finish() কল করা উচিত। নিচের উদাহরণটি দেখায় কিভাবে finish() কল করে সিস্টেমকে ব্রডকাস্ট রিসিভারটি রিসাইকেল করতে দেওয়া যায় এবং একটি ANR এড়ানো যায়:

কোটলিন

val pendingResult = goAsync()

object : AsyncTask<Array<Int>, Int, Long>() {
    override fun doInBackground(vararg params: Array<Int>): Long? {
        // This is a long-running operation
        BubbleSort.sort(params[0])
        pendingResult.finish()
        return 0L
    }
}.execute(data)

জাভা

final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
  @Override
  protected Long doInBackground(Integer[]... params) {
      // This is a long-running operation
      BubbleSort.sort(params[0]);
      pendingResult.finish();
  }
}.execute(data);

তবে, কোডটিকে একটি ধীরগতির ব্রডকাস্ট রিসিভার থেকে অন্য থ্রেডে সরিয়ে নেওয়া এবং goAsync() ব্যবহার করলেও ANR সমস্যার সমাধান হবে না, যদি ব্রডকাস্টটি ব্যাকগ্রাউন্ডে চলে। ANR টাইমআউট তখনও প্রযোজ্য থাকবে।