Не удалось найти утечку памяти

#android #memory-leaks #leakcanary

#Android #утечки памяти #утечка

Вопрос:

Я мог видеть, что leakcanary жалуется на утечку памяти в FingerprintManager.mContext в приведенном ниже коде. Я пробовал статический внутренний класс, слабые ссылки в AsyncTask. Не удалось найти код, создающий утечку памяти.

 class BiometricHelper(var localListener: BiometricHelperListener, val fragmentActivity: FragmentActivity) : BiometricPrompt.AuthenticationCallback() {

    private val mListener: WeakReference<BiometricHelper.BiometricHelperListener> = WeakReference(localListener)

    private val mFragmentActivity: WeakReference<FragmentActivity> = WeakReference(fragmentActivity)

    val mEditor: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(TransfastApplication.getContext())


    private val TAG = BiometricHelper::class.java!!.getName()

    private var mkeyStore: KeyStore? = null
    private var mgenerator: KeyGenerator? = null
    private var mcipher: Cipher? = null
    private val KEYSTORE = "somevalue"
    private val PREFERENCES_KEY_IV = "somevalue"
    private val PREFERENCES_KEY_PASS = "somevalue"
    private var mcryptoObject: BiometricPrompt.CryptoObject? = null

    private var encrypting: Boolean = false
    private var mEmail: String? = null
    private var mPassword: String? = null


    private var mAuthTask: BiometricHelper.UserLoginTask? = null

    private lateinit var myBiometricPrompt: BiometricPrompt


    interface BiometricHelperListener {
        fun onAuthenticationSuccessful(result: BiometricPrompt.AuthenticationResult)
        fun onSavedUserAuthenticationSuccessful(password: String)
        fun onAuthenticationFailed(error: String)
        fun onFingerPrintRegistrationDenied()
        fun onNoBiometricSupport()
    }

    public fun registerAuthentication(email: String, password: String) {
        this.mEmail = email;
        this.mPassword = password;
        mAuthTask = UserLoginTask(this, mEditor, email, password)
        mAuthTask?.execute(null as Void?)
    }

    public fun authenticateExistingUser(email: String) {
        this.mEmail = email
        mAuthTask = UserLoginTask(this, mEditor);
        mAuthTask?.execute(null as Void?)
    }

    private fun startAuth(isRegister: Boolean) {

        val executor = Executors.newSingleThreadExecutor()
        myBiometricPrompt = BiometricPrompt(mFragmentActivity.get()!!, executor, this)
        if (mcryptoObject != null) {

            if (isRegister) {
                val promptInfo = BiometricPrompt.PromptInfo.Builder()

                        .setTitle("Enable Fingerprint")
                        .setSubtitle(mFragmentActivity.get()!!.resources.getString(R.string.fingerprint_confirm_message))
                        //.setDescription(mFragmentActivity.get()!!.resources.getString(R.string.fingerprint_confirm_description))
                        .setNegativeButtonText(mFragmentActivity.get()!!.resources.getString(R.string.text_cancel))
                        .build()
                myBiometricPrompt.authenticate(promptInfo, mcryptoObject!!)
            } else {
                val promptInfo = BiometricPrompt.PromptInfo.Builder()
                        .setTitle("Login")
                        .setSubtitle(mEmail)
                        .setNegativeButtonText(mFragmentActivity.get()!!.resources.getString(R.string.text_cancel))
                        .build()
                myBiometricPrompt.authenticate(promptInfo, mcryptoObject!!)
            }


        }

    }

    public fun isUserRegistered(): Boolean {

        if (mEditor.getString(PREFERENCES_KEY_PASS, null) != null) {
            return true;
        }

        return false;
    }


    public fun checkBiometricSupport(): Boolean {


        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) //currently support library does not support Android M
            return false;

        val keyguardManager = mFragmentActivity.get()?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager

        if (!keyguardManager.isKeyguardSecure) {
            false
        }

        val fingerprintManagerCompat = FingerprintManagerCompat.from(mFragmentActivity.get()!!)

        if (!fingerprintManagerCompat.hasEnrolledFingerprints()) {
            return false;
        }

        if (ActivityCompat.checkSelfPermission(mFragmentActivity.get()!!,
                        Manifest.permission.USE_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) {
            false
        }

        return if (mFragmentActivity.get()!!.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
            true
        } else true
    }


    private fun getKeyStore(): Boolean {
        try {
            mkeyStore = KeyStore.getInstance(KEYSTORE)
            mkeyStore?.load(null) // Create empty keystore
            return true
        } catch (e: KeyStoreException) {
        } catch (e: CertificateException) {
        } catch (e: NoSuchAlgorithmException) {
        } catch (e: IOException) {
        }

        return false
    }


    @TargetApi(Build.VERSION_CODES.M)
    fun createNewKey(forceCreate: Boolean): Boolean {
        try {
            if (forceCreate)
                mkeyStore?.deleteEntry(PrefManager.getInstance().keyAlias)
            if (!mkeyStore?.containsAlias(PrefManager.getInstance().keyAlias)!!) {
                mgenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE)

                mgenerator?.init(KeyGenParameterSpec.Builder(PrefManager.getInstance().keyAlias,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                        .setUserAuthenticationRequired(true)
                        .build()
                )

                mgenerator?.generateKey()
            }
            return true
        } catch (e: Exception) {

        }
        return false
    }

    private fun getCipher(): Boolean {
        try {
            mcipher = Cipher.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES   "/"
                              KeyProperties.BLOCK_MODE_CBC   "/"
                              KeyProperties.ENCRYPTION_PADDING_PKCS7)

            return true
        } catch (e: NoSuchAlgorithmException) {

        } catch (e: NoSuchPaddingException) {

        }

        return false
    }


    public fun isNewFingerprintEnrolled(): Boolean {
        getKeyStore()
        getCipher()
        return !initCipher(Cipher.DECRYPT_MODE);
    }

    @TargetApi(Build.VERSION_CODES.M)
    public fun initCipher(mode: Int): Boolean {
        try {
            mkeyStore?.load(null)
            val keyspec = mkeyStore?.getKey(PrefManager.getInstance().keyAlias, null) as SecretKey

            if (mode == Cipher.ENCRYPT_MODE) {
                mcipher?.init(mode, keyspec)
                var localEditor = mEditor.edit()
                localEditor.putString(PREFERENCES_KEY_IV, Base64.encodeToString(mcipher?.getIV(), Base64.NO_WRAP)).commit()
            } else {
                val iv = Base64.decode(mEditor.getString(PREFERENCES_KEY_IV, ""), Base64.NO_WRAP)
                val ivspec = IvParameterSpec(iv)
                mcipher?.init(mode, keyspec, ivspec)
            }

            return true
        } catch (e: Exception) {
            clearSavedData()
        }

        return false
    }

    @TargetApi(Build.VERSION_CODES.M)
    private fun initCryptObject(): Boolean {
        try {
            mcryptoObject = mcipher?.let { BiometricPrompt.CryptoObject(it) }
            return true
        } catch (ex: Exception) {
        }

        return false
    }

    fun encryptString(password: String) {
        try {
            val bytes = mcipher?.doFinal(password.toByteArray())
            val encryptedText = Base64.encodeToString(bytes, Base64.NO_WRAP)
            var localEditor = mEditor.edit()
            localEditor.putString(PREFERENCES_KEY_PASS, encryptedText).commit()
        } catch (e: Exception) {
            clearSavedData()

        }
    }

    fun decryptString(cipherText: String): String? {

        try {
            val bytes = Base64.decode(cipherText, Base64.NO_WRAP)
            val finalText = String(mcipher!!.doFinal(bytes))
            return finalText;
        } catch (e: Exception) {

            clearSavedData()
        }
        return null;
    }


    class UserLoginTask : AsyncTask<Void, Void, Boolean> {

        private val mEmail: String?
        private val mPassword: String?
        private val mRegister: Boolean? // if false, authenticate instead
        private var biometricHelper: WeakReference<BiometricHelper>

        private var editor: WeakReference<SharedPreferences>

        internal constructor(biometricHelper: BiometricHelper,
                             editor: SharedPreferences,
                             email: String, password: String) {
            this.biometricHelper = WeakReference<BiometricHelper>(biometricHelper)
            this.editor = WeakReference<SharedPreferences>(editor)
            mEmail = email
            mPassword = password
            mRegister = true
        }

        internal constructor(biometricHelper: BiometricHelper,
                             editor: SharedPreferences) {
            this.biometricHelper = WeakReference(biometricHelper)
            this.editor = WeakReference(editor)
            mRegister = false
            mEmail = null
            mPassword = null
        }

        override fun doInBackground(vararg params: Void): Boolean? {
            if (!biometricHelper.get()!!.getKeyStore())
                return false

            if (!biometricHelper.get()!!.createNewKey(false))
                return false

            if (!biometricHelper.get()!!.getCipher())
                return false

            // Inside doInBackground
            if (mRegister!!) {
                biometricHelper.get()!!.encrypting = true

                if (!biometricHelper.get()!!.initCipher(Cipher.ENCRYPT_MODE))
                    return false
            } else {
                biometricHelper.get()!!.encrypting = false
                if (!biometricHelper.get()!!.initCipher(Cipher.DECRYPT_MODE))
                    return false
            }

            return if (!biometricHelper.get()!!.initCryptObject()) false else true

        }

        override fun onPostExecute(success: Boolean) {

            if (success amp;amp; !isCancelled) {
                mRegister?.let { biometricHelper.get()!!.startAuth(it) }
            } else {
                //something not right proceed with normal login
                biometricHelper.get()!!.onFingerprintRegistrationDenied()
            }

        }
    }

    fun onFingerprintRegistrationDenied() {
        mListener.get()?.onFingerPrintRegistrationDenied()
    }


    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
        // super.onAuthenticationError(errorCode, errString)
        Logger.d("onAuthenticationError ****  "   errorCode   "***   "   errString)
        if ((errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON || errorCode == BiometricPrompt.ERROR_USER_CANCELED) amp;amp; encrypting) {
            mListener.get()?.onFingerPrintRegistrationDenied()
        } else if (errorCode == BiometricPrompt.ERROR_CANCELED || errorCode == BiometricPrompt.ERROR_LOCKOUT || errorCode == BiometricPrompt.ERROR_LOCKOUT_PERMANENT) {
            myBiometricPrompt.cancelAuthentication()
            mListener.get()?.onAuthenticationFailed(errString.toString())
        } else {
            super.onAuthenticationError(errorCode, errString)
            //    listener.onAuthenticationFailed(errString.toString())
        }
    }


    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {

        if (encrypting) {
            mPassword?.let { encryptString(it) }
            mListener.get()?.onAuthenticationSuccessful(result)
        } else {
            var encryptedPassword = mEditor.getString(PREFERENCES_KEY_PASS, "");
            val decryptedPassword = decryptString(encryptedPassword)
            if (decryptedPassword != null) {
                mListener.get()?.onSavedUserAuthenticationSuccessful(decryptedPassword)
            } else {
                mListener.get()?.onAuthenticationFailed(SignInFragment.FINGERPRINT_ERROR)
            }
        }


    }

    fun clearSavedData() {
        var localEditor = mEditor.edit()
        localEditor.remove(PREFERENCES_KEY_IV).commit();
        localEditor.remove(PREFERENCES_KEY_PASS).commit();

        try {
            mkeyStore?.deleteEntry(PrefManager.getInstance().keyAlias);

        } catch (ex: java.lang.Exception) {
        } finally {
            PrefManager.getInstance().remove(PrefManager.Key.PREF_KEY_ALIAS)
        }

    }



    fun onDestroy() {
        if (mAuthTask != null) {
            mAuthTask?.cancel(true);
            mAuthTask = null
        }
    }

}
  

Прилагаю скриншот сообщения от leakcanary.
Ошибка утечки памяти в leakcanary
Класс UserLoginTask используется для регистрации, а также для входа пользователя. Класс BiometricHelper используется для аутентификации по отпечаткам пальцев.

Комментарии:

1. Сохранение действий / контекстов в виде полей в классе обычно является для меня тревожным сигналом. В вашем случае вы передаете действие классу BiometricHelper, который он сохраняет как поле. Затем экземпляр этого класса сохраняется внутри запущенной AsyncTask. Утечка здесь может заключаться в том, что AsyncTask сохраняет ссылку на BiometricHelper, которая будет сохранять ссылку на действие до тех пор, пока выполняется задача. Это приводит к тому, что активность выходит за рамки обычного жизненного цикла, так что это может квалифицироваться как утечка памяти.