ANR (व्यू)

सिद्धांत और Jetpack Compose को लागू करना

जब किसी Android ऐप्लिकेशन का यूज़र इंटरफ़ेस (यूआई) थ्रेड बहुत देर तक ब्लॉक रहता है, तब "ऐप्लिकेशन काम नहीं कर रहा है" (एएनआर) गड़बड़ी ट्रिगर होती है. अगर ऐप्लिकेशन फ़ोरग्राउंड में है, तो सिस्टम उपयोगकर्ता को एक डायलॉग दिखाता है. जैसा कि पहली इमेज में दिखाया गया है. ANR डायलॉग से, उपयोगकर्ता को ऐप्लिकेशन को बंद करने का विकल्प मिलता है.

उपयोगकर्ता को दिखाया गया ANR डायलॉग.
पहली इमेज. उपयोगकर्ता को दिखाया गया ANR डायलॉग

ANR एक समस्या है, क्योंकि ऐप्लिकेशन की मुख्य थ्रेड, यूज़र इंटरफ़ेस (यूआई) को अपडेट करने के लिए ज़िम्मेदार होती है. यह उपयोगकर्ता के इनपुट इवेंट को प्रोसेस नहीं कर सकती या ड्रा नहीं कर सकती. इससे उपयोगकर्ता को परेशानी होती है. ऐप्लिकेशन के मुख्य थ्रेड के बारे में ज़्यादा जानने के लिए, प्रोसेस और थ्रेड की खास जानकारी देखें.

इनमें से कोई भी स्थिति होने पर, आपके ऐप्लिकेशन के लिए एएनआर ट्रिगर होता है:

  • इनपुट डिस्पैच करने में समय लगा: अगर आपका ऐप्लिकेशन, इनपुट इवेंट (जैसे कि बटन दबाना या स्क्रीन को छूना) का जवाब पांच सेकंड के अंदर नहीं देता है.
  • सेवा को पूरा करना: अगर आपके ऐप्लिकेशन की ओर से बताई गई कोई सेवा, कुछ सेकंड में Service.onCreate() और Service.onStartCommand()/Service.onBind() को पूरा नहीं कर पाती है.
  • **Service.startForeground() को कॉल नहीं किया गया**: अगर आपका ऐप्लिकेशन, फ़ोरग्राउंड में नई सेवा शुरू करने के लिए Context.startForegroundService() का इस्तेमाल करता है, लेकिन सेवा 5 सेकंड के अंदर startForeground() को कॉल नहीं करती है.
  • ब्रॉडकास्ट ऑफ़ इंटेंट: अगर BroadcastReceiver को तय समय में पूरा नहीं किया गया है. अगर ऐप्लिकेशन में फ़ोरग्राउंड में कोई गतिविधि हो रही है, तो यह टाइम आउट पांच सेकंड का होता है.
  • **JobScheduler इंटरैक्शन**: अगर JobService कुछ सेकंड में JobService.onStartJob() या JobService.onStopJob() से वापस नहीं आता है या अगर उपयोगकर्ता की ओर से शुरू किया गया कोई जॉब शुरू होता है और आपका ऐप्लिकेशन, JobService.onStartJob() को कॉल करने के कुछ सेकंड बाद JobService.setNotification() को कॉल नहीं करता है. Android 13 और इससे पहले के वर्शन को टारगेट करने वाले ऐप्लिकेशन के लिए, एएनआर की सूचना नहीं दी जाती है और न ही इसकी जानकारी ऐप्लिकेशन को दी जाती है. Android 14 और इसके बाद के वर्शन को टारगेट करने वाले ऐप्लिकेशन के लिए, एएनआर की सूचना दी जाती है और इसकी जानकारी ऐप्लिकेशन को दी जाती है.

अगर आपके ऐप्लिकेशन में एएनआर की समस्या आ रही है, तो इस लेख में दिए गए दिशा-निर्देशों का इस्तेमाल करके, समस्या का पता लगाया जा सकता है और उसे ठीक किया जा सकता है.

समस्याएं ठीक करना

समस्या का पता लगाने के बाद, इस सेक्शन में दी गई सलाह का इस्तेमाल करके, आम तौर पर होने वाली समस्याओं को ठीक किया जा सकता है.

मुख्य थ्रेड पर कोड धीरे-धीरे चल रहा है

अपने कोड में उन जगहों का पता लगाएं जहां ऐप्लिकेशन का मुख्य थ्रेड, पांच सेकंड से ज़्यादा समय तक व्यस्त रहता है. अपने ऐप्लिकेशन में, इस्तेमाल के संदिग्ध उदाहरणों को ढूंढें और एएनआर को फिर से जनरेट करने की कोशिश करें.

उदाहरण के लिए, दूसरी इमेज में Traceview टाइमलाइन दिखाई गई है. इसमें मुख्य थ्रेड पांच सेकंड से ज़्यादा समय तक व्यस्त है.

दूसरी इमेज. Traceview टाइमलाइन में, व्यस्त मुख्य थ्रेड दिख रही है

दूसरी इमेज. Traceview टाइमलाइन में व्यस्त मुख्य थ्रेड दिख रहा है

दूसरी इमेज से पता चलता है कि ज़्यादातर गड़बड़ी वाला कोड, onClick(View) हैंडलर में होता है. इसका उदाहरण यहां दिया गया है:

Kotlin

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

Java

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

इस मामले में, आपको मुख्य थ्रेड में चलने वाले काम को वर्कर थ्रेड में ले जाना चाहिए. Android फ़्रेमवर्क में ऐसी क्लास शामिल होती हैं जो टास्क को वर्कर थ्रेड में ले जाने में मदद कर सकती हैं. ज़्यादा जानकारी के लिए, वर्कर थ्रेड देखें.

मुख्य थ्रेड पर I/O

मुख्य थ्रेड पर I/O कार्रवाइयां करने की वजह से, मुख्य थ्रेड पर कार्रवाइयां धीमी हो जाती हैं. इससे ANR की गड़बड़ियां हो सकती हैं. Compose में, डेवलपर अक्सर शुरुआती स्थिति का पता लगाने के दौरान, गलती से डिस्क रीड (जैसे कि SharedPreferences या डेटाबेस कॉल) को ट्रिगर कर देते हैं.

ज़्यादा समय तक चलने वाली I/O कार्रवाइयों को यूज़र इंटरफ़ेस (यूआई) लेयर से अलग तरीके से पूरा करें. ViewModel में withContext(Dispatchers.IO) का इस्तेमाल करें. इससे भी बेहतर है कि डेटा लेयर पर रिपॉज़िटरी का इस्तेमाल करें. हमारा सुझाव है कि सभी आईओ ऑपरेशन को वर्कर थ्रेड में ले जाएं. जैसा कि पिछले सेक्शन में दिखाया गया है.

आई/ओ ऑपरेशन के कुछ उदाहरण, नेटवर्क और स्टोरेज ऑपरेशन हैं. ज़्यादा जानकारी के लिए, नेटवर्क ऑपरेशन करना और डेटा सेव करना लेख पढ़ें.

लॉक का विवाद

कुछ मामलों में, एएनआर की वजह बनने वाला काम सीधे तौर पर ऐप्लिकेशन के मुख्य थ्रेड पर नहीं किया जाता. अगर कोई वर्कर थ्रेड, किसी ऐसे संसाधन पर लॉक रखती है जिसकी ज़रूरत मुख्य थ्रेड को अपना काम पूरा करने के लिए होती है, तो ANR की गड़बड़ी हो सकती है.

उदाहरण के लिए, तीसरी इमेज में Traceview टाइमलाइन दिखाई गई है. इसमें ज़्यादातर काम वर्कर थ्रेड पर किया जाता है.

इमेज 3. Traceview टाइमलाइन, जो वर्कर थ्रेड पर किए जा रहे काम को दिखाती है

तीसरी इमेज. Traceview टाइमलाइन, जो वर्कर थ्रेड पर किए जा रहे काम को दिखाती है

हालांकि, अगर आपके उपयोगकर्ताओं को अब भी एएनआर की समस्या आ रही है, तो आपको Android Device Monitor में मुख्य थ्रेड की स्थिति देखनी चाहिए. आम तौर पर, मुख्य थ्रेड RUNNABLE स्थिति में होता है. ऐसा तब होता है, जब वह यूज़र इंटरफ़ेस (यूआई) को अपडेट करने के लिए तैयार हो और आम तौर पर जवाब देने में सक्षम हो.

हालांकि, अगर मुख्य थ्रेड फिर से काम नहीं कर पाती है, तो वह BLOCKED स्टेट में होती है और इवेंट का जवाब नहीं दे पाती है. पांचवीं इमेज में दिखाए गए तरीके से, Android Device Monitor पर स्थिति Monitor या Wait के तौर पर दिखती है.

इमेज 4. मॉनिटर की स्थिति में मुख्य थ्रेड

चौथी इमेज. मॉनिटर स्टेटस में मुख्य थ्रेड

नीचे दिए गए ट्रेस में, ऐप्लिकेशन की मुख्य थ्रेड दिखाई गई है. यह थ्रेड, किसी संसाधन के लिए इंतज़ार कर रही है:

...
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)
...

ट्रेस की समीक्षा करने से, आपको उस कोड का पता लगाने में मदद मिल सकती है जो मुख्य थ्रेड को ब्लॉक करता है. नीचे दिया गया कोड, उस लॉक को होल्ड करने के लिए ज़िम्मेदार है जो पिछले ट्रेस में मुख्य थ्रेड को ब्लॉक करता है:

Kotlin

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])
            }
}

Java

@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]);
      }
  }
}

एक और उदाहरण, ऐप्लिकेशन की मुख्य थ्रेड है. यह वर्कर थ्रेड से नतीजे का इंतज़ार कर रही है. इसे यहां दिए गए कोड में दिखाया गया है. ध्यान दें कि Kotlin में wait() और notify() का इस्तेमाल करने का सुझाव नहीं दिया जाता है. इसमें एक साथ कई कार्रवाइयों को मैनेज करने के लिए, अपने तरीके होते हैं. Kotlin का इस्तेमाल करते समय, अगर हो सके, तो Kotlin के लिए खास तौर पर बनाए गए तरीकों का इस्तेमाल करें.

Kotlin

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()
        }
    }
}

Java

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, Semaphore का इस्तेमाल करने वाली थ्रेड के साथ-साथ, रिसॉर्स पूल (जैसे, डेटाबेस कनेक्शन का पूल) या अन्य म्यूचल एक्सक्लूज़न (म्यूटेक्स) मैकेनिज़्म शामिल हैं.

आपको उन सभी लॉक का आकलन करना चाहिए जो आपके ऐप्लिकेशन ने संसाधनों पर लगाए हैं. हालांकि, अगर आपको एएनआर से बचना है, तो आपको उन लॉक पर ध्यान देना चाहिए जो मुख्य थ्रेड के लिए ज़रूरी संसाधनों पर लगाए गए हैं.

पक्का करें कि लॉक कम से कम समय के लिए रखे गए हों. इससे भी बेहतर यह होगा कि आप यह आकलन करें कि ऐप्लिकेशन को लॉक की ज़रूरत है या नहीं. अगर आपको लॉक का इस्तेमाल करके यह तय करना है कि वर्कर थ्रेड की प्रोसेसिंग के आधार पर यूज़र इंटरफ़ेस (यूआई) को कब अपडेट करना है, तो वर्कर और मुख्य थ्रेड के बीच कम्यूनिकेट करने के लिए, onProgressUpdate() और onPostExecute() जैसे तरीकों का इस्तेमाल करें.

ब्रॉडकास्ट रिसीवर को सूचना मिलने में समय लग रहा है

ऐप्लिकेशन, ब्रॉडकास्ट रिसीवर की मदद से ब्रॉडकास्ट मैसेज का जवाब दे सकते हैं. जैसे, फ़्लाइट मोड को चालू या बंद करना या कनेक्टिविटी की स्थिति में बदलाव करना. जब कोई ऐप्लिकेशन ब्रॉडकास्ट मैसेज को प्रोसेस करने में बहुत ज़्यादा समय लेता है, तब एएनआर की समस्या होती है.

एएनआर इन मामलों में होता है:

  • ब्रॉडकास्ट रिसीवर ने onReceive तरीके को तय समय में पूरा नहीं किया है.
  • ब्रॉडकास्ट रिसीवर, goAsync को कॉल करता है और PendingResult ऑब्जेक्ट पर finish को कॉल नहीं कर पाता.

आपके ऐप्लिकेशन को BroadcastReceiver के onReceive तरीके में सिर्फ़ छोटी कार्रवाइयां करनी चाहिए. हालांकि, अगर ब्रॉडकास्ट मैसेज की वजह से आपके ऐप्लिकेशन को ज़्यादा जटिल प्रोसेसिंग की ज़रूरत है, तो आपको टास्क को ViewModel (Kotlin कोरूटीन, स्कोप, और डिस्पैचर का इस्तेमाल करके) पर ट्रांसफ़र करना चाहिए. अगर टास्क को पूरा होने में कुछ सेकंड लगते हैं, तो किसी भी तरह के स्टेट होल्डर पर ट्रांसफ़र करें. अगर टास्क को पूरा होने में कुछ सेकंड से ज़्यादा समय लगता है, तो WorkManager पर ट्रांसफ़र करें.

Traceview जैसे टूल का इस्तेमाल करके, यह पता लगाया जा सकता है कि आपका ब्रॉडकास्ट रिसीवर, ऐप्लिकेशन की मुख्य थ्रेड पर लंबे समय तक चलने वाले ऑपरेशन को पूरा करता है या नहीं. उदाहरण के लिए, छठे डायग्राम में ब्रॉडकास्ट रिसीवर की टाइमलाइन दिखाई गई है. यह मुख्य थ्रेड पर करीब 100 सेकंड तक मैसेज प्रोसेस करता है.

इमेज 5. Traceview टाइमलाइन में, मुख्य थ्रेड पर `BroadcastReceiver` का काम दिखाया गया है

पांचवीं इमेज. Traceview की टाइमलाइन में, मुख्य थ्रेड पर BroadcastReceiver काम करते हुए दिखाया गया है

BroadcastReceiver के onReceive() तरीके पर ज़्यादा समय तक चलने वाली कार्रवाइयां करने की वजह से ऐसा हो सकता है. इसे इस उदाहरण में दिखाया गया है:

Kotlin

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

Java

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

ऐसी स्थितियों में, ज़्यादा समय तक चलने वाली कार्रवाई को IntentService में ले जाने का सुझाव दिया जाता है, क्योंकि यह अपना काम पूरा करने के लिए वर्कर थ्रेड का इस्तेमाल करता है. नीचे दिए गए कोड में, ज़्यादा समय तक चलने वाली कार्रवाई को पूरा करने के लिए, IntentService का इस्तेमाल करने का तरीका बताया गया है:

Kotlin

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

Java

@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 टाइमलाइन में वर्कर थ्रेड को सौंपा गया काम दिखाया गया है.

इमेज 6. Traceview टाइमलाइन में, वर्कर थ्रेड पर प्रोसेस किया गया ब्रॉडकास्ट मैसेज दिखाया गया है

छठी इमेज. Traceview टाइमलाइन में, वर्कर थ्रेड पर प्रोसेस किया गया ब्रॉडकास्ट मैसेज दिखाया गया है

आपका ब्रॉडकास्ट रिसीवर, सिस्टम को यह सूचना देने के लिए goAsync() का इस्तेमाल कर सकता है कि उसे मैसेज को प्रोसेस करने के लिए ज़्यादा समय चाहिए. हालांकि, आपको PendingResult ऑब्जेक्ट पर finish() को कॉल करना चाहिए. यहां दिए गए उदाहरण में, finish() को कॉल करने का तरीका बताया गया है. इससे सिस्टम, ब्रॉडकास्ट रिसीवर को रीसाइकल कर सकता है और एएनआर से बच सकता है:

Kotlin

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)

Java

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() का इस्तेमाल करने से एएनआर ठीक नहीं होगा. एएनआर का टाइम आउट अब भी लागू होता है.