Memory leak caused by Android Sonic Recognizer Dialog

Keywords: Attribute Android SDK

Memory leaks were found when checking memory usage today. After preliminary testing, the problem was located in Xunfei's speech recognition function.
You should know that using the official UI provided by the voice of Xunfei, you need to create a dialog to record.

As follows:

if (mIatDialog == null) {
            // Initialize dictation Dialog. If you only use UI dictation, you don't need to create SpeechRecognizer
            mIatDialog = new RecognizerDialog(context, new MyInitListener());
            //Setting up callback interface
            mIatDialog.setListener(new MyRecognizerDialogListener());
        }
        //Start dictating
        mIatDialog.show();

mIatDialog = new RecognizerDialog(context, new MyInitListener());

When we create RecognizerDialog here, we create a dialog. RecognizerDialog eventually inherits the Dialog class in Android SDK. Why do you still hold Context for a Dialog after the Activity is destroyed?
Through Android Studio memory analysis and location, after destroying Activity, the activity is also referenced by an object, tracing the past and finding that it is the SpeechRecognizer class of Xunfei.

In the official example code, if you use the UI of Xunfei to start recording, create a RecognizerDialog directly, and execute show() to display OK. Another way is to create a SpeechRecognizer object directly.

// 1. Create the SpeechRecognizer object, the second parameter: Pass InitListener on local dictation
SpeechRecognizer mIat= SpeechRecognizer.createRecognizer(context, null);

SpeechRecognizer is actually the core class of voice. Whether you start recording in that way, SpeechRecognizer will have an instance. The first way is to simplify development. When you create Recognizer Dialog, SpeechRecognizer was created on behalf of others. By positioning, Context references come from SpeechRecognizer class

Below is the SpeechRecognizer class part of the code, you can see that this is a typical example:

public final class SpeechRecognizer extends v {
    //Here is a static object reference for SpeechRecognizer
    private static SpeechRecognizer a = null;
    private Z d = null;
    private SpeechRecognizerAidl e = null;
    private SpeechRecognizer.a f = null;
    private InitListener g = null;
      public static SpeechRecognizer getRecognizer() {
        return a;
    }

    //Use Context to create singletons
      public static synchronized SpeechRecognizer createRecognizer(Context var0, InitListener var1) {
        Object var2 = b;
        synchronized(b) {
            if(a == null && null != SpeechUtility.getUtility()) {
                a = new SpeechRecognizer(var0, var1);
            }
        }

        return a;
    }
}

So the problem is obvious. Speech Recognizer's singleton holds an activity reference. Activity can't be destroyed naturally. The problem has been found. How can we solve it?

Solve

I don't know if I wasn't careful enough. In the official documents, I didn't see the release of SpeechRecognizer, so that after I found that I was holding Context by singleton, I didn't look at SpeechRecognizer's code carefully, so I started to find a way to null this instance and found a feasible solution. Then I looked at SpeechRecognizer's code and found out

Fuck! SpeechRecognizer TM has an open destroy() method!!!!!

  public boolean destroy() {
        boolean var1 = true;
        SpeechRecognizerAidl var2 = this.e;
        if(null != var2) {
            var2.destory();
        }

        synchronized(this) {
            this.e = null;
        }

        Z var3 = this.d;
        if(var3 != null) {
            var1 = var3.destroy();
        }

        if(var1 && (var1 = super.destroy())) {
            Object var4 = b;
            synchronized(b) {
                a = null;
            }

            SpeechUtility var8 = SpeechUtility.getUtility();
            if(null != var8) {
                O.a("Destory asr engine.");
                var8.setParameter("engine_destroy", "engine_destroy=asr");
            }
        }

        return var1;
    }

So, in fact, to solve the problem, just execute SpeechRecognizer's destory() when Activity finish():

 public void destory() {
        SpeechRecognizer.getRecognizer().destroy();
    }

Here's how to destroy the singleton. This singleton is a static private property. How can we put it in Null?

This is what I saw at the beginning when I was looking for information leaked from Xunfei's memory on the internet. Some of the meanings were that the address could not be found.

The idea is to retrieve a SpeechRecognizer instance by reflection, then take out the static singleton and force blanking, because the member attribute is static, so in the new SpeechRecognizer instance and the original instance, this attribute points to the same block of memory, blanks it, and cuts off the reference to context.

Although this method is not necessary here, it is also a viable solution to destroy a singleton class that does not provide destory methods.

     try {  
            Class clazz=Class.forName("com.iflytek.cloud.SpeechRecognizer");
            //Get member attribute a (singleton object)  
            Field field=clazz.getDeclaredField("a");  
            //Setting private Attribute Accessible
            if (field.isAccessible()==false) {  
                field.setAccessible(true);  
            }  
            //Vacant attributes
            field.set(null, null);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  

Posted by Jason Batten on Thu, 11 Jul 2019 18:16:10 -0700