Android配置变更时EditText的getLayout()为空问题及单行文本获取方案
Hey, 我看你遇到了设备旋转时App崩溃的问题,这是个很常见的Android配置变更坑,我来帮你拆解原因并给出解决方案。
问题回顾
你在多行EditText的TextWatcher里通过getLayout()获取第一行文本,正常场景没问题,但旋转屏幕时触发了NullPointerException,错误日志显示getLayout()返回了null。你的代码和错误日志如下:
相关代码
public class CreateNoteActivity extends AppCompatActivity { private EditText etNote, etNoteTitle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_note); setTitle("Write Note"); etNoteTitle = findViewById(R.id.etNoteTitle); etNote = findViewById(R.id.etNote); etNote.requestFocus(); etNote.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { int start = etNote.getLayout().getLineStart(0); int end = etNote.getLayout().getLineEnd(0); String title = etNote.getText().subSequence(start, end).toString(); etNoteTitle.setText(title); } @Override public void afterTextChanged(Editable editable) { } }); } }
错误日志
1-21 16:47:28.792 12534-12534/com.example.haadee.noteitdown E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.haadee.noteitdown, PID: 12534 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.haadee.noteitdown/com.example.haadee.noteitdown.ui.CreateNoteActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.text.Layout.getLineStart(int)' on a null object reference at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2678) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2743) at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4560) at android.app.ActivityThread.-wrap19(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1496) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6165) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:778) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.text.Layout.getLineStart(int)' on a null object reference at com.example.haadee.noteitdown.ui.CreateNoteActivity$1.onTextChanged(CreateNoteActivity.java:39) at android.widget.TextView.sendOnTextChanged(TextView.java:8231) at android.widget.TextView.setText(TextView.java:4512) at android.widget.TextView.setText(TextView.java:4366) at android.widget.EditText.setText(EditText.java:89) at android.widget.TextView.setText(TextView.java:4341) at android.widget.TextView.onRestoreInstanceState(TextView.java:4232) at android.view.View.dispatchRestoreInstanceState(View.java:15767) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3240) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3240) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3240) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3240) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3240) at android.view.View.restoreHierarchyState(View.java:15745) at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2106) at android.app.Activity.onRestoreInstanceState(Activity.java:1051) at android.app.Activity.performRestoreInstanceState(Activity.java:1006) at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1196) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2651) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2743) at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4560) at android.app.ActivityThread.-wrap19(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1496) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6165) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:778)
为什么会崩溃?
设备旋转属于配置变更,Android系统会销毁当前Activity然后重建。重建过程中,系统会自动恢复EditText的内容,这个恢复操作会触发TextWatcher的onTextChanged回调。但此时EditText还没完成布局测量和渲染,getLayout()方法还没初始化,返回了null,你直接调用getLineStart(0)就会抛出空指针异常。
解决方案
这里有几个靠谱的处理方式,你可以根据需求选择:
方案1:先判断Layout是否有效,再处理文本
在onTextChanged里先检查getLayout()是否不为null,只有当Layout存在时才用它获取第一行;如果Layout还没初始化,就用换行符分割的方式兜底:
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { Layout layout = etNote.getLayout(); String title; if (layout != null) { // Layout已初始化,用它获取精确的第一行(包括自动换行的情况) int start = layout.getLineStart(0); int end = layout.getLineEnd(0); title = charSequence.subSequence(start, end).toString(); } else { // Layout未初始化,用换行符分割获取第一行 String fullText = charSequence.toString(); int lineBreakPos = fullText.indexOf('\n'); title = lineBreakPos != -1 ? fullText.substring(0, lineBreakPos) : fullText; } etNoteTitle.setText(title); }
方案2:完全不依赖Layout,直接通过文本内容获取第一行
如果你的场景不需要考虑EditText自动换行的情况(只关注用户手动输入的换行),可以直接通过换行符\n来分割文本,这种方法完全避开了Layout的问题,逻辑更简单稳定:
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { String fullText = charSequence.toString(); int firstLineBreak = fullText.indexOf('\n'); String title = firstLineBreak == -1 ? fullText : fullText.substring(0, firstLineBreak); etNoteTitle.setText(title); }
这个方案的优势是适配所有场景,包括配置变更时的恢复过程,不会出现空指针。
方案3:禁止Activity在配置变更时重建(可选)
如果你的Activity在横竖屏时使用同一套布局,可以在AndroidManifest.xml中给Activity添加configChanges属性,让系统不销毁重建Activity:
<activity android:name=".CreateNoteActivity" android:configChanges="orientation|screenSize|smallestScreenSize"> </activity>
这样旋转屏幕时Activity不会重建,也就不会触发这个崩溃场景,但只适合布局不随方向变化的情况。
总结
我个人推荐方案2,因为它逻辑简单,不依赖UI渲染状态,能覆盖所有使用场景;如果需要精确匹配EditText的自动换行(比如文本过长自动折行的情况),那就用方案1。
内容的提问来源于stack exchange,提问作者Asgar Ahmed

