studio - textbox android




NullPointerException su dispositivi Meizu in Editor.updateCursorPositionMz (6)

Finalmente ho avuto la possibilità di mettere le mani su un Meizu. Come pensavo, l'arresto si verifica ogni volta che l'utente fa clic su un campo per ottenere lo stato attivo.

Nel mio caso, avevo alcuni android.support.design.widget.TextInputEditText all'interno di TextInputLayout s. La semplice sostituzione di questi messaggi TextInputEditText con AppCompatEditText risolto il problema, in questo modo:

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

Il comportamento rimane lo stesso (poiché TextInputEditText estende AppCompatEditText ). Tuttavia, non ho ancora trovato la causa principale del problema.

Ultimamente, ci sono stati arresti anomali sulla mia app Android, solo sui dispositivi Meizu (M5c, M5s, M5 Note). Versione Android: 6.0.

Ecco la traccia dello stack completo:

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)

Non esiste una relazione diretta con il mio codice (anche nelle stracktraces di altri thread). So solo che succede ogni volta in un frammento in cui ci sono TextView. Potrebbe accadere quando un TextView si sta focalizzando ma non ho modo di esserne sicuro. Ovviamente non posso riprodurre il bug, a meno che non compro un Meizu.

Inoltre, poiché il metodo principale si chiama updateCursorPositionMz , mi sembra che questo potrebbe essere un problema interno nel FlymeOS di Meizu ("Mz" = "Meizu"?).

Qualcuno ha già avuto questo problema, conosce la causa e come risolverlo?

Grazie.


Ho basato la mia soluzione su FixedTextInputEditText come menzionato in https://github.com/android-in-china/Compatibility/issues/11#issuecomment-427560370 .

Prima di tutto, ho creato un'istanza TextInputEditText fissa:

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

Ma poi dovrei sostituire tutti i miei usi TextInputEditText con MeizuTextInputEditText che non è qualcosa che puoi facilmente fare su una base di codice più grande. Inoltre, quando si creano viste future, è sempre necessario considerare l'utilizzo di MeizuTextInputEditText invece di quello "rotto". Dimenticarsene introdurrebbe facilmente di nuovo problemi di produzione.

Quindi la correzione finale consiste nella classe di visualizzazione personalizzata e insieme alla libreria ViewPump ( https://github.com/InflationX/ViewPump ) possiamo facilmente farlo. Proprio come spiegato nei documenti è necessario registrare un intercettore personalizzato che assomiglia a questo:

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

E la registrazione dell'interceptor personalizzato viene eseguita esattamente come nei documenti configurando un ViewPump su onCreate della tua attività:

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

Come puoi vedere, MeizuTextInputEditText il MeizuTextInputEditText se viene rilevato un dispositivo Meizu. In questo modo il riflesso non viene attivato per i dispositivi che non ne hanno bisogno. Anche questo metodo è una classe di attività di base che ho da cui ogni altra attività si estende nel mio progetto, quindi ogni attività che viene avviata nel mio progetto E in cui il dispositivo è un Meizu avrà automaticamente la correzione!


Nel mio caso, ho verificato che l'uso di AppCompatEditText invece di TextInputEditText effettivamente impedito arresti anomali, ma non siamo riusciti a utilizzare questa soluzione. Stiamo usando un SDK con viste che estendono TextInputEditText , quindi passare ad AppCompatEditText richiederebbe la copia / modifica di un bel po 'del codice SDK nel nostro progetto.

Ho provato a impostare il suggerimento su TextInputEditText e TextInputLayout , ma ho finito per vedere un doppio suggerimento (come il testo sfocato, e sono sicuro di non aver bevuto troppo).

Ho dato un'occhiata al problema GitHub collegato a @Andrew: github.com/android-in-china/Compatibility/issues/11

In questo numero, spiegano che la causa principale è un problema su Meizu quando TextInputEditText.getHint() è diverso da TextInputEditText.mHint .

Quando un TextInputEditText trova all'interno di un TextInputLayout e il suggerimento è specificato in xml in TextInputEditText , la libreria di supporto "sposta" sostanzialmente il suggerimento nel TextInputLayout contenente: lo imposta sul contenitore e quindi lo imposta su null nel testo di modifica.

Questa fonte che lo fa è in 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);
      }

Quindi quando si chiama TextInputEditText.getHint() , verrà restituito il suggerimento del contenitore.

Questa incoerenza tra getHint() (il valore del suggerimento) e mHint (null) sembra rappresentare un problema per i dispositivi Meizu

Ho trovato un altro modo per evitare questo problema.

Sui dispositivi Meizu, io:

1) reimpostare a livello di TextInputEditText il suggerimento di TextInputEditText su ciò che era stato impostato originariamente da xml (chiamando il suo getHint() sovrascritto getHint() che restituisce il suggerimento del contenitore).

2) imposta il colore del suggerimento di TextInputEditText su trasparente, per evitare l'effetto doppio / sfocato del suggerimento:

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

Nessuna delle varianti sopra ha funzionato per me senza modifiche.

La mia app utilizza frammenti, a volte TextInputEditText viene utilizzato senza TextInputLayout, al momento l'aggiornamento all'ultima versione di AndroidX non era un'opzione, al momento la sostituzione di TextInputEditText non era un'opzione.

La mia versione (basata su quella soluzione e sulla correzione di 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()
    }
}

Successivamente trova e sostituisci TextInputEditText con MyInputEditText in tutti i file di layout e di codice.


Sto usando Kotlin e Fragments e sto semplicemente riparando in modo ricorsivo tutti gli input di testo in 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
    }
}

Rimuovi il suggerimento da XML : da TextInputLayout o TextInputEditText .

Per componenti materiali

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

Per supporto alla progettazione

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

Nel tuo codice imposta un suggerimento a livello di codice:

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

Testato su Meizu M5S , Android 6.0





meizu