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.
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 .
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 TextInputLayout
s. En remplaçant simplement ces TextInputEditText
s par AppCompatEditText
s, 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.
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
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());
}
}
}
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!
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
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
}
}
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
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.
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