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

Tur*_*ter 62 android textview meizu

最近,我的Android应用程序出现了崩溃,在魅族设备上(M5c,M5s,M5 Note).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)
Run Code Online (Sandbox Code Playgroud)

与我的代码没有直接关系(即使在其他线程的stracktraces中).我只知道它每次都出现在有TextView的片段中.当TextView获得焦点时可能会发生这种情况,但我无法确定.当然我不能重现这个bug,除非我买了一个魅族.

此外,由于调用了top方法updateCursorPositionMz,我认为这可能是魅族FlymeOS中的内部问题("Mz"="魅族"?).

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

谢谢.

Tur*_*ter 51

最后我有机会把手放在魅族身上.正如我想的那样,每次用户点击字段以获得焦点时都会发生崩溃.

在我的情况下,我有一些android.support.design.widget.TextInputEditText内幕TextInputLayout.简单地用TextInputEditTexts 替换这些s AppCompatEditText修复了问题,如下:

<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>
Run Code Online (Sandbox Code Playgroud)

行为保持不变(自TextInputEditText扩展以来AppCompatEditText).我仍然没有找到问题的根本原因.

  • 这个修复在AndroidX上适用于我.我在com.google.android.material.textfield.TextInputLayout中有一个com.google.android.material.textfield.TextInputEditText.用androidx.appcompat.widget.AppCompatEditText替换了EditText,崩溃就消失了. (6认同)
  • 这个解决方案实际上对我不起作用.我需要在TextInputLayout中扩展一些AppCompatEditText以使其工作.有人提出了不同的解决方法吗? (2认同)
  • 我找到了下一个解决方案:https://github.com/android-in-china/Compatibility/issues/11 (2认同)

And*_*ger 7

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

  • @MichaelGroenendijk它已在1.1.0-alpha07中发布,请参阅https://github.com/material-components/material-components-android/releases/tag/1.1.0-alpha07 (2认同)

Car*_*men 5

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

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

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

在这个问题,他们解释说,当根本原因是魅族的一个问题TextInputEditText.getHint()是来自不同TextInputEditText.mHint

当a TextInputEditText位于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);
      }
Run Code Online (Sandbox Code Playgroud)

然后,当您调用时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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


dir*_*ert 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但随后我将不得不替换我所有的TextInputEditText用法,MeizuTextInputEditText而这在更大的代码库上不是很容易做到的。此外,在创建未来视图时,您始终需要考虑使用MeizuTextInputEditText'broken'视图而不是 'broken'视图。忘记它很容易再次引入生产问题。

所以最终的修复由自定义视图类和 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

通过在活动的 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());
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我仅在MeizuTextInputEditText检测到魅族设备时才增加该值。这样,不需要的设备就不会触发反射。此外,此方法是我拥有的基本 Activity 类,我的项目中的所有其他活动都从中扩展,因此在我的项目中启动的每个活动以及设备是魅族的每个活动都将自动修复!