魅族设备上的NullPointerException在Editor.updateCursorPositionMz中


71

最近,在魅族设备(M5c,M5s,M5 Note)上,我的Android应用程序发生了崩溃。Android版本:6.0。

这是完整的堆栈跟踪:

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)

与我的代码没有直接关系(即使在其他线程的stracktrace中也是如此)。我只知道它每次都在有TextViews的Fragment中发生。当TextView获得焦点时可能会发生,但我无法确定。当然,除非购买魅族,否则我无法重现该错误。

另外,由于调用了top方法updateCursorPositionMz,在我看来,这可能是魅族FlymeOS的内部问题(“ Mz” =“ Meizu”?)。

有没有人已经遇到过这个问题,知道原因以及如何解决?

谢谢。


1
我也是。编译/目标sdk = 28,在编译/目标为27时没有问题
本地主机

3
更新。受影响的魅族设备在Android 6和7
localhost,

@localhost Mmh值得一提的是,自从我更新到targetSdk 28以来,我也遇到了这些问题。但是我现在还不能真正降级到27 ...
Turboblaster

1
更新我的应用程序从SDK 27至28后,我可以证实,这个问题在Android 6和7魅族设备
斯特芬

1
这个问题已记录在github.com/android-in-china/Compatibility/issues/11中,并提供了一种解决方法。希望能有所帮助。
qezt

Answers:


58

更新(2019年8月8日)

正如@ andreas-wenger,@ waseefakhtar和@ vadim-kotov所述,此修复程序现已包含在com.google.android.material:material:1.1.0-alpha08及以后的版本中。

旧答案

终于,我有机会把手放在了魅族上。如我所料,每次用户单击字段以获取焦点时,都会发生崩溃。

就我而言,我有一些android.support.design.widget.TextInputEditText内部TextInputLayout。简单地替换这些TextInputEditText与SAppCompatEditText固定的问题,像这样S:

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

行为保持不变(因为TextInputEditTextextends AppCompatEditText)。我仍然没有找到问题的根本原因。


2
该解决方案实际上对我不起作用。我需要在TextInputLayout内对AppCompatEditText进行一些扩展才能使其正常工作。有人提出了不同的解决方法吗?
rui.mendes '18

6
此修复程序对我在AndroidX上有效。我在com.google.android.material.textfield.TextInputLayout内有com.google.android.material.textfield.TextInputEditText。用androidx.appcompat.widget.AppCompatEditText替换了EditText,然后崩溃消失了。
mliu


这有帮助,谢谢。但是,删除EditText中的所有符号时发生崩溃。
lewkka

7

这只是在Android lib的材料组件中修复的,请参见:https : //github.com/material-components/material-components-android/pull/358


1
根据链接中的讨论,此修复程序应在2019年5月下旬在下一个版本中发布。(当前版本为1.1.0-alpha06,因此下一个版本将是任何版本)
mgR19年

2
@MichaelGroenendijk它在1.1.0-alpha07发布,请参阅github.com/material-components/material-components-android/...
瓦迪姆·科托夫

5

我基于https://github.com/android-in-china/Compatibility/issues/11#issuecomment-427560370中FixedTextInputEditText提到的解决方案。

首先,我创建了一个固定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);
    }
}

但是,那时我将不得不替换所有TextInputEditText用法,MeizuTextInputEditText而这在较大的代码库上是不容易实现的。同样,在创建将来的视图时,您始终需要考虑使用MeizuTextInputEditText而不是“残缺的”视图。忘记它会很容易再次引起生产问题。

因此,最终的解决方案包括自定义视图类,以及ViewPump库(https://github.com/InflationX/ViewPump),我们可以轻松地做到这一点。就像在文档中解释的那样,您需要注册一个看起来像这样的自定义拦截器:

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

就像在文档中一样,通过在活动的onCreate上设置一个ViewPump来​​注册该自定义拦截器:

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

如您所见,MeizuTextInputEditText如果检测到魅族设备,我只会给充气。这样,对于不需要反射的设备不会触发反射。同样,此方法是我具有的基本Activity类,项目中的所有其他活动都从该类中扩展,因此,在我的项目中启动且设备为魅族的每个活动都将自动修复!


感谢您的回答!节省了我很多时间。我只是想知道您究竟在哪里检查该设备是否是魅族,也许您忘记了将其添加到响应中,或者只是提到您正在执行此操作,但是在代码库中的其他位置(可能在注册此拦截器之前?)
Guilherme Santos

@GuilhermeSantos我刚刚使用viewpump init更新了答案。如文档中所述进行设置,但仅适用于魅族设备。如果您还需要针对其他设备的自定义充气拦截(以修复其他问题),则可以轻松地在此处进行检查,并添加更多拦截器!
dirkvranckaert

5

就我而言,我验证了使用AppCompatEditText而不是TextInputEditText确实避免了崩溃,但是我们无法使用此解决方案。我们使用的SDK具有扩展的views TextInputEditText,因此切换到sdkAppCompatEditText需要将相当多的sdk代码复制/修改到我们的项目中。

我尝试同时在TextInputEditText和上设置提示TextInputLayout,但最终还是看到了一个双重提示(例如模糊的文字,而且我确定自己喝的不多)。

我看了@Andrew链接的GitHub问题:https : //github.com/android-in-china/Compatibility/issues/11

在该期中,他们解释说,根本原因是与上TextInputEditText.getHint()不同的魅族上的问题TextInputEditText.mHint

当aTextInputEditText在a内时TextInputLayout,并且提示是在xml上指定的TextInputEditText,则支持库基本上将提示“移动”到包含TextInputLayout:它在容器上设置提示,然后在编辑文本上将其设置为null。

此操作的源位于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);
      }

然后,当您调用时TextInputEditText.getHint(),它将返回容器的提示。

getHint()(提示值)和mHint(null)之间的这种不一致似乎为魅族设备带来了问题

我找到了避免此问题的另一种方法。

在魅族设备上,我:

1)以编程方式将TextInputEditText的提示重置为最初从xml设置的提示(通过调用其覆盖的值getHint(),该返回的值将返回容器的提示)。

2)将TextInputEditText提示颜色设置为透明,以避免双重/模​​糊提示效果:

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

您可以分享描述重置TextInputEditText提示的代码吗?在哪里调用hackFixHintsForMeizu方法?
Zhebzhik Babich

@ZhebzhikBabich我不确定我是否理解您的第一个问题。如果您想知道如何从中删除提示TextInputEditText,可以查看的来源TextInputLayout.setEditText()。我用代码段和链接更新了答案。对于第二个问题:我们只在一个屏幕中遇到此问题,因此对于我们来说此解决方案是可以的。如果您在TextInputEditText整个应用程序中都有很多东西,则可能需要一个不同的解决方案(如果可能的话,最好替换TextInputEditText一下AppCompatEditText
Carmen

好,谢谢。我只是认为我需要通过重写getHint()方法来清除提示。
Zhebzhik Babich

我只是意识到,由于某种原因,我对第二个问题的回答部分被省略了。我们从活动的onCreate()方法中调用此方法:hackFixHintsForMeizu(editText1, editText2, editText3, ...)。我们只需要在一个屏幕中进行一次破解。
卡门,

2

从xml中删除提示:从TextInputLayoutTextInputEditText中删除提示

用于材料组件

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

提供设计支持

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

在您的代码中以编程方式设置提示:

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

魅族M5S和Android 6.0上进行了测试


尝试了一些带有提示的情况,并发现在两种布局(android.support.design.widget.TextInputLayout和android.support.design.widget.TextInputEditText)上都以XML添加提示可以解决崩溃问题。
АлексейЮр

本地主机,在您的代码中以编程方式将提示设置为TextInputEditText。如果将提示设置为TextInputLayout,它将崩溃吗?
Zhebzhik Babich

2

都添加了提示TextInputLayoutTextInputEditText为我修复了崩溃:

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

最后,以TextInputEditText编程方式重置提示,以避免提示文本的颜色过深:

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

已在配备Android 6.0的魅族MX6上进行验证


在TextInputLayout中添加实际提示(android:hint =“ @ string / login”),在TextInputEditText中添加空提示(android:hint =“”)
wiz

这不是正确的解决方案。使用此修复程序将介绍由于这里的框架错误的更多的崩溃- > stackoverflow.com/questions/45898228/...
雷李

1

我正在使用Kotlin和Fragments,只是递归地修复了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
    }
}

那么xml呢?您在哪里设置提示?在TextInputLayout或TextInputEditText中?
Zhebzhik Babich

我的方法不需要编辑xml。而是,此代码递归地找到所有editText并将hackFixHintsForMeizu函数应用到它们。易于实施,但在加载片段或活动时可能会使应用变慢。
Andrew

我的意思是,如果我已将TextInputLayout的提示设置为“ myHint”,而未在xml中为TextInputEditText设置提示,editText.hint = editText.hint则将null设置为null。这种骇客在这种情况下是否可以正常工作,或者该应用程序会像往常一样在魅族上崩溃?
Zhebzhik Babich

根据我的实践,它在这种情况下有效。仅当hint != null和时才发生崩溃myHint == null。但我认为,为添加nullability检查hint不会导致崩溃。如果可以,我将撤消我的答案编辑。
Andrew


0

未经修改,以上任何变体均不适用于我。

我的应用程序使用片段,有时不使用TextInputLayout时使用TextInputEditText,目前不选择升级到最新的AndroidX,此时也不选择替换TextInputEditText。

我的版本(基于这些解决方案和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()
    }
}

之后,在所有布局和代码文件中使用MyInputEditText查找并替换TextInputEditText。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.