2019-08-13
關鍵字:自定義EditText、java.lang.ClassCastException: java.lang.String cannot be cast to android.text.Editable
錯誤發生在繼承自官方 EditText 實現自定義視圖的場景下。當重寫了父類中的
public void setText(CharSequence text, BufferType type)
方法時就報了異常,異常堆棧信息如下:
08-13 02:34:31.342 5859-5859/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.my.pkg, PID: 5859 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.my.pkg/com.my.pkg.MainActivity}: android.view.InflateException: Binary XML file line #10: Error inflating class com.my.pkg.IPEditText at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2195) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: android.view.InflateException: Binary XML file line #10: Error inflating class com.my.pkg.IPEditText at android.view.LayoutInflater.createView(LayoutInflater.java:621) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:697) at android.view.LayoutInflater.rInflate(LayoutInflater.java:756) at android.view.LayoutInflater.inflate(LayoutInflater.java:492) at android.view.LayoutInflater.inflate(LayoutInflater.java:397) at android.view.LayoutInflater.inflate(LayoutInflater.java:353) at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290) at android.app.Activity.setContentView(Activity.java:1929) at com.my.pkg.MainActivity.onCreate(MainActivity.java:21) at android.app.Activity.performCreate(Activity.java:5231) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Constructor.constructNative(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at android.view.LayoutInflater.createView(LayoutInflater.java:595) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:697) at android.view.LayoutInflater.rInflate(LayoutInflater.java:756) at android.view.LayoutInflater.inflate(LayoutInflater.java:492) at android.view.LayoutInflater.inflate(LayoutInflater.java:397) at android.view.LayoutInflater.inflate(LayoutInflater.java:353) at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290) at android.app.Activity.setContentView(Activity.java:1929) at com.my.pkg.MainActivity.onCreate(MainActivity.java:21) at android.app.Activity.performCreate(Activity.java:5231) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to android.text.Editable at android.widget.EditText.getText(EditText.java:75) at com.my.pkg.IPEditText.textInitialization(IPEditText.java:55) at com.my.pkg.IPEditText.<init>(IPEditText.java:43) at java.lang.reflect.Constructor.constructNative(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at android.view.LayoutInflater.createView(LayoutInflater.java:595) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:697) at android.view.LayoutInflater.rInflate(LayoutInflater.java:756) at android.view.LayoutInflater.inflate(LayoutInflater.java:492) at android.view.LayoutInflater.inflate(LayoutInflater.java:397) at android.view.LayoutInflater.inflate(LayoutInflater.java:353) at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290) at android.app.Activity.setContentView(Activity.java:1929) at com.my.pkg.MainActivity.onCreate(MainActivity.java:21) at android.app.Activity.performCreate(Activity.java:5231) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245) at android.app.ActivityThread.access$800(ActivityThread.java:135) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method)
這份異常的關鍵在於最后標紅加粗部分。它描述的是遇到了類型轉換異常,String 不能被強轉成 Editable。
到底怎么回事呢?
原因是在重寫了父類的 setText(CharSequence, BufferType) 后沒有調用父類中的這個方法,即:
@Override public void setText(CharSequence text, BufferType type) { // super.setText(text, type); }
那為什么不執行父類的這個方法,竟然會導致類型轉換異常呢?
這個得去跟蹤一下 TextView 的源碼,即 EditText 的父類,EditText 雖然繼承自 TextView,但它並沒有多少自己的邏輯,主要還是靠 TextView。
在 TextView 中,用於記錄顯示的文本的是 mText 變量。
private CharSequence mText;
這個變量的值默認是 null,在構造方法中被指向一個空的 String 常量:

在 TextView 的構造方法中,會去讀取 xml 中配置在 android:text 屬性中的字符串值:

隨后會調用 setText 方法來為 mText 賦值,將 xml 中的文本值賦給 mText 變量。

前面的 text 變量本身就是 CharSequence 類型的,默認的 setText 方法里面也並沒有什么什么特別的操作,就是簡單地將 text 的值賦給了 mText 而已,但是在這里有一個很重要的性質的改變,就是原本的指向普通空值 String 的 mText 在經過了 setText 以后變成了指向一個 CharSequence 類型對象。
這一性質轉換本身並不會引發什么,但是 EditText 的加載過程中會去調用 getText() 方法。EditText 方法中的 getText() 方法的實現很短,如下所示:
@Override public Editable getText() { return (Editable) super.getText(); }
它會去向父類,即 TextView,通過 getText() 方法要來值,再不由分說強轉成 Editable 返回。TextView 中的 getText() 的實現是什么?就是簡單的將 mText 對象返回嘛:
public CharSequence getText() { return mText; }
到這,就足夠清晰了吧?
如果沒有執行父類的 setText(CharSequence,BufferType) 方法,那么,mText 就默認指向一個空值 String 類對象。在 EditText 的加載過程中會去調用 getText() 方法,這樣一來,就要強制將 String 類型轉換成 Editable 類型了,這種轉換是不正確的。
那怎么解決呢?我就是不想調用父類的 setText(CharSequence,BufferType) 方法,能避免這個錯誤嗎?
我不知道啊。mText 是 private 修飾的,不能直接更改。但是感覺可以嘗試一下用反射的方式來解決,給 mText 一個正兒八經的 CharSequence 類對象就可以了嘛。如果你實在有興趣,可以仔細去研讀一下 TextView 的源碼以尋求一個解決方案了,我反正是沒興趣。
