web-dev-qa-db-fra.com

NullPointerException sur les appareils Meizu dans Editor.updateCursorPositionMz

Dernièrement, il y a eu des accidents sur mon application Android, sur les appareils Meizu niquement (M5c, M5s, M5 Note). Android version: 6.0.

Voici la trace de pile complète:

Fatal Exception: Java.lang.NullPointerException: Attempt to invoke virtual method 'int Android.text.Layout.getLineForOffset(int)' on a null object reference
   at Android.widget.Editor.updateCursorPositionMz(Editor.Java:6964)
   at Android.widget.Editor.updateCursorsPositions(Editor.Java:1760)
   at Android.widget.TextView.getUpdatedHighlightPath(TextView.Java:5689)
   at Android.widget.TextView.onDraw(TextView.Java:5882)
   at Android.view.View.draw(View.Java:16539)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15492)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ViewGroup.recreateChildDisplayList(ViewGroup.Java:3719)
   at Android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.Java:3699)
   at Android.view.View.updateDisplayListIfDirty(View.Java:15443)
   at Android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.Java:286)
   at Android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.Java:292)
   at Android.view.ThreadedRenderer.draw(ThreadedRenderer.Java:327)
   at Android.view.ViewRootImpl.draw(ViewRootImpl.Java:3051)
   at Android.view.ViewRootImpl.performDraw(ViewRootImpl.Java:2855)
   at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:2464)
   at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:1337)
   at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:6819)
   at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:894)
   at Android.view.Choreographer.doCallbacks(Choreographer.Java:696)
   at Android.view.Choreographer.doFrame(Choreographer.Java:631)
   at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:880)
   at Android.os.Handler.handleCallback(Handler.Java:815)
   at Android.os.Handler.dispatchMessage(Handler.Java:104)
   at Android.os.Looper.loop(Looper.Java:207)
   at Android.app.ActivityThread.main(ActivityThread.Java:5969)
   at Java.lang.reflect.Method.invoke(Method.Java)
   at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:830)
   at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:720)

Il n'y a pas de relation directe avec mon code (même dans les stracktraces des autres threads). Je sais seulement que cela arrive à chaque fois dans un fragment dans lequel il y a TextViews. Cela peut arriver lorsqu'un TextView gagne le focus mais je n'ai aucun moyen d'en être sûr. Bien sûr, je ne peux pas reproduire le bogue, à moins d’acheter un Meizu.

De plus, comme la méthode top est appelée updateCursorPositionMz, il me semble que cela pourrait être un problème interne dans FlymeOS de Meizu ("Mz" = "Meizu"?).

Quelqu'un at-il déjà eu ce problème, connaît la cause et comment le résoudre?

Merci.

58
Turboblaster

Mise à jour (8 août 2019)

Comme @ andreas-wenger, @waseefakhtar et @ vadim-kotov ont été mentionnés, le correctif est maintenant inclus à partir de com.google.Android.material: material: 1.1.0-alpha08 .

Ancienne réponse

Finalement, j'ai eu la chance de poser mes mains sur un Meizu. Comme je le pensais, le blocage se produit chaque fois que l'utilisateur clique sur un champ pour obtenir le focus.

Dans mon cas, il y avait quelques Android.support.design.widget.TextInputEditText à l'intérieur de TextInputLayouts. En remplaçant simplement ces TextInputEditTexts par AppCompatEditTexts, le problème a été résolu, comme suit:

<Android.support.design.widget.TextInputLayout
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:hint="...">

    <Android.support.v7.widget.AppCompatEditText
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"/>

</Android.support.design.widget.TextInputLayout>

Le comportement reste le même (puisque TextInputEditText s'étend AppCompatEditText). Je n'ai toujours pas trouvé la cause du problème.

44
Turboblaster

Cela venait juste d'être corrigé dans les composants matériels de Android lib, voir: https://github.com/material-components/material-components-Android/pull/358

7
Andreas Wenger

Dans mon cas, j’ai vérifié que l’utilisation de AppCompatEditText au lieu de TextInputEditText évitait effectivement les plantages, mais nous ne pouvions pas utiliser cette solution. Nous utilisons un sdk avec des vues qui étendent TextInputEditText, donc passer à AppCompatEditText nécessiterait de copier/modifier une bonne partie du code sdk dans notre projet.

J'ai essayé de placer l'indice TextInputEditText et TextInputLayout, mais j'ai fini par voir un double indice (comme un texte flou, et je suis sûr que je n'ai pas trop bu).

J'ai jeté un coup d'oeil à la question de GitHub liée à @Andrew: https://github.com/Android-in-china/Compatibility/issues/11

Dans ce numéro, ils expliquent que la cause première est un problème sur Meizu lorsque TextInputEditText.getHint() est différent de TextInputEditText.mHint.

Quand un TextInputEditText est à l'intérieur d'un TextInputLayout et que l'indicateur est spécifié en xml sur le TextInputEditText, la bibliothèque de support "déplace" fondamentalement l'indicateur vers le TextInputLayout qui le contient: le définit sur le conteneur, puis le définit sur null sur le texte modifié.

Cette source qui fait cela est dans TextInputLayout.setEditText () :

    // If we do not have a valid hint, try and retrieve it from the EditText, if enabled
    if (hintEnabled) {
      if (TextUtils.isEmpty(hint)) {
        // Save the hint so it can be restored on dispatchProvideAutofillStructure();
        originalHint = this.editText.getHint();
        setHint(originalHint);
        // Clear the EditText's hint as we will display it ourselves
        this.editText.setHint(null);
      }

Ensuite, lorsque vous appelez TextInputEditText.getHint(), le résultat sera renvoyé.

Cette incohérence entre getHint() (la valeur de l'indice) et mHint (null) semble poser un problème pour les périphériques Meizu.

J'ai trouvé un autre moyen d'éviter ce problème.

Sur les appareils Meizu, je:

1) réinitialisez par programmation l'indicateur de la variable TextInputEditText à sa configuration d'origine, à partir du xml (en appelant son getHint() surchargé, qui renvoie l'indicateur du conteneur).

2) réglez la couleur de conseil de la variable TextInputEditText sur transparent afin d'éviter l'effet de conseil double/flou:

private void hackFixHintsForMeizu(TextInputEditText... editTexts) {
    String manufacturer = Build.MANUFACTURER.toUpperCase(Locale.US);
    if (manufacturer.contains("MEIZU")) {
        for (TextInputEditText editText : editTexts) {
            editText.setHintTextColor(Color.TRANSPARENT);
            editText.setHint(editText.getHint());
        }
    }
}
5
Carmen

J'ai basé ma solution sur la FixedTextInputEditText mentionnée dans https://github.com/Android-in-china/Compatibility/issues/11#issuecomment-42756037 .

Tout d'abord, j'ai créé une instance fixe TextInputEditText:

public class MeizuTextInputEditText extends TextInputEditText {
    public MeizuTextInputEditText(Context context) {
        super(context);
    }

    public MeizuTextInputEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MeizuTextInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public CharSequence getHint() {
        try {
            return getMeizuHintHack();
        } catch (Exception e) {
            return super.getHint();
        }
    }

    private CharSequence getMeizuHintHack() throws NoSuchFieldException, IllegalAccessException {
        Field textView = TextView.class.getDeclaredField("mHint");
        textView.setAccessible(true);
        return (CharSequence) textView.get(this);
    }
}

Mais alors je devrais remplacer toutes mes utilisations TextInputEditText par MeizuTextInputEditText, ce que vous ne pouvez pas faire facilement avec une base de code plus grande. De même, lors de la création de vues futures, vous devez toujours envisager d'utiliser la variable MeizuTextInputEditText à la place de la vue "brisée". Oublier cela créerait facilement des problèmes de production.

Ainsi, le correctif final se compose de la classe de vue personnalisée et de la bibliothèque ViewPump ( https://github.com/InflationX/ViewPump ), nous pouvons facilement le faire. Comme expliqué dans la documentation, vous devez enregistrer un intercepter personnalisé qui ressemble à celui-ci:

public class TextInputEditTextInterceptor implements Interceptor {
    @Override
    public InflateResult intercept(Chain chain) {
        InflateRequest request = chain.request();
        View view = inflateView(request.name(), request.context(), request.attrs());

        if (view != null) {
            return InflateResult.builder()
                    .view(view)
                    .name(view.getClass().getName())
                    .context(request.context())
                    .attrs(request.attrs())
                    .build();
        } else {
            return chain.proceed(request);
        }
    }

    @Nullable
    private View inflateView(String name, Context context, AttributeSet attrs) {
        if (name.endsWith("TextInputEditText")) {
            return new MeizuTextInputEditText(context, attrs);
        }
        return null;
    }
}

Et l'enregistrement de cet intercepteur personnalisé se fait comme dans la documentation en configurant un ViewPump sur le onCreate de votre activité:

@Override
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ViewPump.Builder viewPumpBuilder = ViewPump.builder();
    if (isMeizuDevice()) {
        viewPumpBuilder.addInterceptor(new TextInputEditTextInterceptor());
    }
    ViewPump.init(viewPumpBuilder.build());
}

Comme vous pouvez le constater, je ne gonfle la MeizuTextInputEditText que si un appareil Meizu est détecté. Ainsi, la réflexion n'est pas déclenchée pour les appareils qui n'en ont pas besoin. De plus, cette méthode est une classe d’activité de base que je possède et à partir de laquelle toutes les autres activités sont étendues dans mon projet. Ainsi, chaque activité démarrée dans mon projet ET où l’appareil est un Meizu aura le correctif automatiquement!

3
dirkvranckaert

L'ajout d'indices sur TextInputLayout et TextInputEditText a corrigé le crash pour moi:

    <Android.support.design.widget.TextInputLayout
        Android:id="@+id/text_input_layout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:hint="@string/login"
        app:hintAnimationEnabled="false">

        <Android.support.design.widget.TextInputEditText
            Android:id="@+id/text_input_edit_text"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:hint="@string/login" />
        </Android.support.design.widget.TextInputLayout>

Enfin, réinitialisez le conseil de TextInputEditText par programme pour éviter la couleur très sombre du texte de conseil:

editText = findViewById(R.id.text_input_edit_text);
editText.setHint("");

Vérifié sur Meizu MX6 avec Android 6.0

2
er-mo

J'utilise Kotlin et Fragments et je corrige simplement de manière récursive toutes les entrées de texte dans onViewCreated.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    fixTextInputEditText(view) // call this in onViewCreated
}

private fun fixTextInputEditText(view: View) {
    val manufacturer = Build.MANUFACTURER.toUpperCase(Locale.US)
    if ("MEIZU" in manufacturer) {
        val views = getAllTextInputs(view)
        views.forEach(::hackFixHintsForMeizu)
    }
}

private fun getAllTextInputs(v: View): List<TextInputEditText> {
    if (v !is ViewGroup) {
        val editTexts = mutableListOf<TextInputEditText>()
        (v as? TextInputEditText)?.let {
            editTexts += it
        }
        return editTexts
    }

    val result = mutableListOf<TextInputEditText>()
    for (i in 0 until v.childCount) {
        val child = v.getChildAt(i)
        result += getAllTextInputs(child)
    }
    return result
}

private fun hackFixHintsForMeizu(editText: TextInputEditText) {
    if (editText.hint != null) {
        editText.setHintTextColor(Color.TRANSPARENT)
        editText.hint = editText.hint
    }
}
1
Andrew

Supprime l'indice du xml : soit de TextInputLayout ou TextInputEditText.

Pour les composants matériels

<com.google.Android.material.textfield.TextInputLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

    <com.google.Android.material.textfield.TextInputEditText
            Android:id="@+id/text_input_edit_text"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"/>
</com.google.Android.material.textfield.TextInputLayout>

Pour le support de conception

<Android.support.design.widget.TextInputLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content">

    <Android.support.design.widget.TextInputEditText
            Android:id="@+id/text_input_edit_text"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"/>
</Android.support.design.widget.TextInputLayout>

Dans votre code, indiquez par programme:

val myTextInputEditText = findViewById<TextInputEditText>(R.id.text_input_edit_text)
myTextInputEditText.hint = "Your hint"

Testé sur Meizu M5S, Android 6.0

1
localhost

Aucune des variantes ci-dessus n'a fonctionné pour moi sans modifications.

Mon application utilise des fragments, TextInputEditText étant parfois utilisé sans TextInputLayout, la mise à niveau vers la dernière version d'AndroidX n'était pas une option pour le moment, le remplacement de TextInputEditText n'était pas non plus une option pour le moment.

Ma version (basée sur ces solutions et le correctif de Google):

import Android.os.Build
import Java.util.*
import Android.content.Context
import Android.support.design.widget.TextInputEditText
import Android.util.AttributeSet
import Android.widget.TextView
import Android.support.design.widget.TextInputLayout
import Android.view.inputmethod.EditorInfo
import Android.view.inputmethod.InputConnection
import Java.lang.reflect.Field
import Java.lang.reflect.Method
import Android.support.design.R

class MyInputEditText(context: Context?, attrs: AttributeSet?,defStyleAttr:Int) : TextInputEditText(context, attrs,defStyleAttr){

    constructor(context: Context?, attrs: AttributeSet?):this(context,attrs,R.attr.editTextStyle)
    constructor(context: Context?):this(context,null,R.attr.editTextStyle)


    private val buggyMeizu = ("meizu") in Build.MANUFACTURER.toLowerCase(Locale.US)

    private lateinit var getTextInputLayoutMethod:Method
    private lateinit var providesHintMethod:Method
    private lateinit var mHintField:Field

    init {
        if (buggyMeizu) {
            getTextInputLayoutMethod=TextInputEditText::class.Java.getDeclaredMethod("getTextInputLayout")
            getTextInputLayoutMethod.isAccessible=true

            providesHintMethod=TextInputLayout::class.Java.getDeclaredMethod("isProvidingHint")
            providesHintMethod.isAccessible=true

            mHintField=TextView::class.Java.getDeclaredField("mHint")
            mHintField.isAccessible=true
        }
    }


    private fun getTILProvidesHint():Boolean {
        val layout=getTIL()
        if (layout!=null) {
            val result=providesHintMethod.invoke(layout) as Boolean
            return result;
        } else {
            return false
        }
    }

    private fun getTIL():TextInputLayout? = getTextInputLayoutMethod.invoke(this) as TextInputLayout?

    private fun getBaseHint():CharSequence? = mHintField.get(this) as CharSequence?

    override fun getHint(): CharSequence? {
        if (!buggyMeizu) {
            return super.getHint()
        } else {
            val layout=getTIL()
            return if (layout != null && (getTILProvidesHint()) ) 
                layout.hint
            else 
                provideHintWrapped()
        }
    }


    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
        val needHint=(outAttrs.hintText==null)
        val ic = super.onCreateInputConnection(outAttrs)
        if (buggyMeizu) {
            if (ic != null && needHint) {
                outAttrs.hintText = this.provideHintWrapped()
            }
        }
        return ic
    }

    private fun provideHintWrapped():CharSequence? {

        val hintFromLayout=getHintFromLayoutMine()
        if (hintFromLayout!=null) {
            return hintFromLayout
        } else {
            val baseHint=getBaseHint()
            if (baseHint!=null) {
                return baseHint
            } else {
                return null
            }
        }

    }
    private fun getHintFromLayoutMine(): CharSequence? {
        val layout = getTIL()
        return layout?.hint
    }

    override fun onAttachedToWindow() {

        if (buggyMeizu) {

            val baseHint=getBaseHint()

            if (getTIL() != null
                    && getTILProvidesHint()
                    && baseHint == null) {
                this.hint=""
            }
        }

        super.onAttachedToWindow()
    }
}

Après cela, recherchez et remplacez TextInputEditText par MyInputEditText dans tous les fichiers de mise en page et de code.

0
dkzm

Ce correctif est maintenant inclus dans la nouvelle version de composants de matériaux ici: https://github.com/material-components/material-components-Android/releases/tag/1.1.0-alpha09

0
waseefakhtar