android - NullPointerException en dispositivos Meizu en Editor.updateCursorPositionMz
textview (8)
En mi caso, verifiqué que el uso de
AppCompatEditText
lugar de
TextInputEditText
efectivamente evitó bloqueos, pero no pudimos usar esta solución.
Estamos usando un sdk con vistas que extienden
TextInputEditText
, por lo que cambiar a
AppCompatEditText
requeriría copiar / modificar un poco el código sdk en nuestro proyecto.
Intenté establecer la sugerencia tanto en
TextInputEditText
como en
TextInputLayout
, pero terminé viendo una doble sugerencia (como un texto borroso, y estoy seguro de que no bebí demasiado).
Eché un vistazo al problema de GitHub vinculado por @Andrew: github.com/android-in-china/Compatibility/issues/11
En ese problema, explican que la causa raíz es un problema en Meizu cuando
TextInputEditText.getHint()
es diferente de
TextInputEditText.mHint
.
Cuando
TextInputEditText
está dentro de
TextInputLayout
, y la sugerencia se especifica en xml en
TextInputEditText
, la biblioteca de soporte básicamente "mueve" la sugerencia al
TextInputLayout
contiene: lo establece en el contenedor y luego lo establece en null en el texto de edición.
Esta fuente que hace esto está en 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);
}
Luego, cuando llame a
TextInputEditText.getHint()
, devolverá la sugerencia del contenedor.
Esta incoherencia entre
getHint()
(el valor de la sugerencia) y
mHint
(nulo) parece plantear un problema para los dispositivos Meizu
Encontré otra forma de evitar este problema.
En los dispositivos Meizu, yo:
1) restablece de manera programática la sugerencia de
TextInputEditText
a su configuración original de xml (al llamar a su
getHint()
anulada, que devuelve la sugerencia del contenedor).
2) establezca el color de la sugerencia de
TextInputEditText
en transparente, para evitar el efecto de doble / borrosa sugerencia:
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());
}
}
}
Últimamente, se han producido fallos en mi aplicación de Android, solo en dispositivos Meizu (M5c, M5s, M5 Note). Versión de Android: 6.0.
Aquí está la traza de pila completa:
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)
No hay una relación directa con mi código (incluso en los stracktraces de otros hilos). Solo sé que sucede cada vez que en un fragmento en el que hay vistas de texto. Puede estar ocurriendo cuando un TextView está ganando enfoque, pero no tengo forma de estar seguro. Por supuesto no puedo reproducir el error, a menos que compre un Meizu.
Además, dado que el método superior se llama
updateCursorPositionMz
, me parece que esto puede ser un problema interno en el FlymeOS de Meizu ("Mz" = "Meizu"?).
¿Alguien ya ha tenido este problema, conoce la causa y cómo solucionarlo?
Gracias.
Esto solo se solucionó en los componentes de material para Android lib, consulte: https://github.com/material-components/material-components-android/pull/358
Estoy utilizando Kotlin y Fragmentos y acabo de corregir de forma recursiva todas las entradas de texto en 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
}
}
Finalmente tuve la oportunidad de poner mis manos en un Meizu. Como pensé, el bloqueo se produce cada vez que el usuario hace clic en un campo para obtener el foco.
En mi caso, tuve algunos
android.support.design.widget.TextInputEditText
dentro de
TextInputLayout
s.
Simplemente reemplazando estos
TextInputEditText
s con
AppCompatEditText
s solucionamos el problema, de esta manera:
<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>
El comportamiento sigue siendo el mismo (ya que
TextInputEditText
extiende
AppCompatEditText
).
Sin embargo, todavía no he encontrado la causa raíz del problema.
La adición de la sugerencia en
TextInputLayout
y
TextInputEditText
solucionó el bloqueo para mí:
<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>
Finalmente, reinicie la sugerencia de
TextInputEditText
programación para evitar el color muy oscuro del texto de la sugerencia:
editText = findViewById(R.id.text_input_edit_text);
editText.setHint("");
Verificado en Meizu MX6 con Android 6.0
Ninguna de las variantes anteriores funcionó para mí sin modificaciones.
Mi aplicación usa fragmentos, TextInputEditText a veces se usa sin TextInputLayout, la actualización a la última versión de AndroidX no era una opción en este momento, reemplazar el TextInputEditText tampoco era una opción en este momento.
Mi versión (basada en esa solución y la solución 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()
}
}
Después de eso, busque y reemplace TextInputEditText con MyInputEditText en todos los archivos de diseño y código.
FixedTextInputEditText
mi solución en el
FixedTextInputEditText
como se menciona en
https://github.com/android-in-china/Compatibility/issues/11#issuecomment-427560370
.
En primer lugar, creé una instancia de
TextInputEditText
fija:
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);
}
}
Pero luego tendría que reemplazar todos mis usos
TextInputEditText
con
MeizuTextInputEditText
que no es algo que pueda hacer fácilmente en una base de código más grande.
Además, al crear vistas futuras, siempre debe considerar el uso de
MeizuTextInputEditText
lugar de la vista "rota".
Olvidarse de eso fácilmente volvería a presentar problemas de producción.
Por lo tanto, la solución final consiste en la clase de vista personalizada y junto con la biblioteca ViewPump ( https://github.com/InflationX/ViewPump ) podemos hacerlo fácilmente. Tal como se explica en los documentos, debe registrar un interceptor personalizado que se parece a este:
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;
}
}
Y el registro de ese interceptor personalizado se realiza igual que en los documentos mediante la configuración de un ViewPump en el onCreate de su actividad:
@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());
}
Como puede ver, solo
MeizuTextInputEditText
el
MeizuTextInputEditText
si se detecta un dispositivo Meizu.
De esa manera, la reflexión no se activa para los dispositivos que no la necesitan.
Además, este método es una clase de actividad básica que tengo de la cual todas las demás actividades se extienden en mi proyecto, por lo que todas las actividades que se inician en mi proyecto Y donde el dispositivo es un Meizu tendrán la solución automáticamente.
Eliminar la sugerencia de xml : ya sea de TextInputLayout o TextInputEditText .
Para componentes de material
<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>
Para soporte de diseño
<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>
En el código establecido, sugerencia programática :
val myTextInputEditText = findViewById<TextInputEditText>(R.id.text_input_edit_text)
myTextInputEditText.hint = "Your hint"
Probado en Meizu M5S , Android 6.0