最近、私のAndroidアプリ、Meizuデバイスでクラッシュが発生しましたのみ(M5c、M5s、M5注)。 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であっても)。 TextViewがあるFragmentでeverytimeが発生することしか知りません。 TextViewがフォーカスを獲得しているときに発生する可能性がありますが、確信が持てません。もちろん、Meizuを購入しない限り、バグを再現することはできません。
また、topメソッドはupdateCursorPositionMz
と呼ばれるため、これはMeizuのFlymeOSの内部問題(「Mz」=「Meizu」?)のように思えます。
誰もがすでにこの問題を抱えていて、原因とその修正方法を知っていますか?
ありがとう。
最後に、私はMeizuに手を置く機会がありました。思ったように、ユーザーがフィールドをクリックしてフォーカスを取得するたびにクラッシュが発生します。
私の場合、TextInputLayout
sの中にAndroid.support.design.widget.TextInputEditText
がいくつかありました。これらのTextInputEditText
sをAppCompatEditText
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>
動作は同じままです(TextInputEditText
はAppCompatEditText
を拡張するため)。しかし、私はまだ問題の根本原因を発見していません。
これは、Android libのマテリアルコンポーネントで修正されました。以下を参照してください。 https://github.com/material-components/material-components-Android/pull/358
私の場合、AppCompatEditText
の代わりにTextInputEditText
を使用することで実際にクラッシュを防止できることを確認しましたが、このソリューションを使用することはできませんでした。 TextInputEditText
を拡張するビューを持つSDKを使用しているため、AppCompatEditText
に切り替えるには、かなりのSDKコードをプロジェクトにコピー/変更する必要があります。
TextInputEditText
とTextInputLayout
の両方にヒントを設定しようとしましたが、最終的には二重のヒントが表示されました(テキストがぼやけていたり、飲みすぎなかったと確信しています)。
@AndrewによってリンクされたGitHubの問題を確認しました: https://github.com/Android-in-china/Compatibility/issues/11
その問題では、TextInputEditText.getHint()
がTextInputEditText.mHint
と異なる場合、根本原因はMeizuの問題であると説明しています。
TextInputEditText
がTextInputLayout
の内部にあり、TextInputEditText
のヒントがxmlで指定されている場合、サポートライブラリは基本的に、含まれるTextInputLayout
にヒントを「移動」します。
これを行うこのソースは 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デバイスでは、私は:
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());
}
}
}
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("");
Meizu MX6でAndroid 6.0で検証済み
https://github.com/Android-in-china/Compatibility/issues/11#issuecomment-42756037 に記載されているように、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());
}
ご覧のとおり、Meizuデバイスが検出された場合にのみMeizuTextInputEditText
を拡張します。そうすれば、反射はそれを必要としないデバイスに対してはトリガーされません。また、このメソッドは、他のすべてのアクティビティがプロジェクトで拡張されるベースActivityクラスであるため、プロジェクトで開始され、デバイスがMeizuであるすべてのアクティビティが自動的に修正されます!
私は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のいずれかから.
材料コンポーネントの場合
<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でテスト済み