developer tip

Editor.updateCursorPositionMz의 Meizu 장치에 대한 NullPointerException

optionbox 2020. 12. 9. 08:05
반응형

Editor.updateCursorPositionMz의 Meizu 장치에 대한 NullPointerException


최근에 내 Android 앱에서 Meizu 기기 (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)

내 코드와 직접적인 관련이 없습니다 (다른 스레드의 stracktraces에서도). TextView가있는 Fragment에서 매번 발생한다는 것을 알고 있습니다. TextView가 초점을 맞출 때 발생할 수 있지만 확신 할 방법이 없습니다. 물론 Meizu를 구입하지 않으면 버그를 재현 할 수 없습니다.

또한 top 메서드 updateCursorPositionMz가라고 부르기 때문에 이것이 Meizu의 FlymeOS ( "Mz"= "Meizu"?)의 내부 문제인 것처럼 보입니다.

이미이 문제가 발생한 사람이 있습니까? 원인과 해결 방법을 알고 있습니까?

감사.


업데이트 (2019 년 8 월 8 일)

@ andreas-wenger, @waseefakhtar 및 @ vadim-kotov가 언급했듯이 수정 사항은 이제 com.google.android.material : material : 1.1.0-alpha08 부터 포함됩니다.

이전 답변

마침내 나는 Meizu를 손에 넣을 기회가있었습니다. 내가 생각했듯이, 사용자가 포커스를 얻기 위해 필드를 클릭 할 때마다 충돌이 발생합니다.

제 경우에는 android.support.design.widget.TextInputEditText내부 TextInputLayouts 가있었습니다 . TextInputEditTexts를 AppCompatEditTexts로 바꾸면 다음 같이 문제가 해결되었습니다.

<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). 그래도 문제의 근본 원인을 찾지 못했습니다.


이것은 Android lib의 재료 구성 요소에서 수정되었습니다. https://github.com/material-components/material-components-android/pull/358을 참조하십시오.


제 경우 AppCompatEditText에는 TextInputEditText실제로 대신 사용하면 충돌이 방지 된다는 것을 확인 했지만이 솔루션을 사용할 수 없었습니다. 확장 뷰가있는 sdk를 사용하고 TextInputEditText있으므로로 전환 AppCompatEditText하려면 상당량의 sdk 코드를 프로젝트에 복사 / 수정해야합니다.

TextInputEditText둘 다에 힌트를 설정하려고했지만 TextInputLayout이중 힌트가 표시되었습니다 (흐릿한 텍스트와 같이 너무 많이 마시지 않은 것 같습니다).

@Andrew에 연결된 GitHub 문제를 살펴 보았습니다 : https://github.com/android-in-china/Compatibility/issues/11

이 문제에서 그들은 근본 원인이 Meizu TextInputEditText.getHint()와 다른 경우 문제라고 설명합니다 TextInputEditText.mHint.

a TextInputEditText가 내부에 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) 사이의 이러한 불일치는 Meizu 장치에 문제를 일으키는 것으로 보입니다.

이 문제를 피할 수있는 다른 방법을 찾았습니다.

Meizu 장치에서 I :

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

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

보시다시피 MeizuTextInputEditTextMeizu 장치가 감지되면 팽창합니다 . 이렇게하면 반사가 필요하지 않은 장치에 대해 반사가 트리거되지 않습니다. 또한이 메서드는 다른 모든 활동이 내 프로젝트에서 확장되는 기본 활동 클래스이므로 내 프로젝트에서 시작된 모든 활동과 장치가 Meizu 인 경우 자동으로 수정됩니다!


xml에서 힌트를 제거하십시오 : TextInputLayout 또는 TextInputEditText .

재료 구성 요소

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

Meizu M5S , Android 6.0 에서 테스트 됨


모두에서 힌트를 추가 TextInputLayout하고 TextInputEditText나를 위해 충돌을 고정 :

    <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이 설치된 Meizu MX6에서 확인 됨


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

이 수정 사항은 이제 https://github.com/material-components/material-components-android/releases/tag/1.1.0-alpha09 의 새로운 material-components 릴리스에 포함됩니다.


위의 변형은 수정없이 저에게 효과가 없었습니다.

내 앱은 조각을 사용하고 TextInputEditText는 때때로 TextInputLayout없이 사용되며 현재 최신 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()
    }
}

그 후 모든 레이아웃 및 코드 파일에서 TextInputEditText를 MyInputEditText로 찾아서 바꿉니다.

참고 URL : https://stackoverflow.com/questions/51891415/nullpointerexception-on-meizu-devices-in-editor-updatecursorpositionmz

반응형