英文:
Re-inflating view in handler.post when getView() is null
问题
最近,我用处理程序(handlers)和newSingleThreadExecutors替换了我的应用程序中所有已弃用的AsyncTask代码。在从远程服务器检索响应数据后,我在代码的handler.post部分更新UI。
我个人从未能够重现与此有关的任何问题,但在某些设备(主要是oppo、redmi、vivo等)在某些实际条件下,getView()返回null,我的临时尝试重新膨胀视图失败。崩溃次数大大增加了:
异常java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法'java.lang.Object android.content.Context.getSystemService(java.lang.String)'
我的代码大致轮廓:
public class ResultFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.result, container, false);
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
assert bundle != null;
String query_url = bundle.getString("query_url");
send_request(query_url);
}
void send_request(String... urls) {
Handler handler = new Handler(Looper.getMainLooper());
Executors.newSingleThreadExecutor().execute(() -> {
.....
handler.post(() -> {
context = getContext();
final TextView mTextView;
final WebView mWebView;
if (getView() != null) {
mTextView = getView().findViewById(R.id.count);
mWebView = getView().findViewById(R.id.result);
} else {
View view = LayoutInflater.from(context).inflate(R.layout.result, null); // 崩溃点
mTextView = view.findViewById(R.id.count);
mWebView = view.findViewById(R.id.result);
}
根据生命周期文档,我应该能够使用这段代码获取视图。我也理解尝试像这样重新膨胀代码是危险的(可能会导致崩溃!)。但是当getView()返回null时,我应该如何处理呢?
正如我所说,我从未能够重现这些崩溃。所以我愿意尝试任何可能有效的方法。
一般信息来说,我目标SDK版本是33。
英文:
I recently replaced all the deprecated AsyncTask code in my apps with handlers and newSingleThreadExecutors. After retrieving response data from a remote server, I update the UI in the handler.post section of the code.
I've never personally been able to reproduce any problems with this, but on some devices (mostly oppo's, redmi's, vivo's, etc) under some real-world conditions, getView() returns null and my stop-gap attempt to re-inflate the view fails. The number of crashes has increased by a lot:
Exception java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference
Rough outline of my code:
public class ResultFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.result, container, false);
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
assert bundle != null;
String query_url = bundle.getString("query_url");
send_request(query_url);
}
void send_request(String... urls) {
Handler handler = new Handler(Looper.getMainLooper());
Executors.newSingleThreadExecutor().execute(() -> {
.....
handler.post(() -> {
context = getContext();
final TextView mTextView;
final WebView mWebView;
if (getView() != null) {
mTextView = getView().findViewById(R.id.count);
mWebView = getView().findViewById(R.id.result);
} else {
View view = LayoutInflater.from(context).inflate(R.layout.result, null); <-- crash
mTextView = view.findViewById(R.id.count);
mWebView = view.findViewById(R.id.result);
}
My understanding from the lifecycle documentation is that I should be able to get the view with this code. And I do understand that trying to re-inflate the code like this is a dangerous proposition (crashes might occur!). But how do I do so when getView() returns null?
As I say, I've never been able to replicate these crashes. So I'm open to trying anything that might work.
For general information, I'm targeting sdk version 33.
答案1
得分: 1
Your code will crash if the fragment view (or the entire Fragment instance) is destroyed as a result of user leaving your app or screen.
A quick fix for your issue is to cancel the handler runnable execution when the fragment view is destroyed.
@Override
public void onDestroyView() {
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
}
For future development of your app you should look over more advance API to deal with this sort of issue. Ex: RxJava, Kotlin coroutines to name a few.
Small tip that will probably help you to reproduce the crash on any device/emulator - activate the developer option: "Don't keep activity", press the button that makes the network request and then immediately close the application. The thread will post a runnable that will execute after the fragment/view-fragment is destroyed -> NPE crash.
英文:
Your code will crash if the fragment view (or the entire Fragment instance) is destroyed as a result of user leaving your app or screen.
A quick fix for your issue is to cancel the handler runnable execution when the fragment view is destroyed.
@Override
public void onDestroyView() {
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
}
For future development of your app you should look over more advance API to deal with this sort of issue. Ex: RxJava, Kotlin coroutines to name a few.
Small tip that will probably help you to reproduce the crash on any device/emulator - activate the developer option: "Don't keep activity", press the button that makes the network request and then immediately close the application. The thread will post a runnable that will execute after the fragment/view-fragment is destroyed -> NPE crash.
答案2
得分: 0
以下是翻译好的部分:
- "You wouldn't do this at all." -> 你根本不应该这样做。
- "If you don't have a view, reinflating it isn't going to do what you expect." -> 如果你没有视图,重新膨胀它不会产生你期望的效果。
- "It would, at best, create a new set of views that are in memory only and not displayed on the screen." -> 充其量,它会创建一组仅存在于内存中而不显示在屏幕上的新视图。
- "In other words it would be pointless." -> 换句话说,这将毫无意义。
- "Also, that's not what your problem is." -> 另外,这并不是你的问题所在。
- "Your problem is that the context is null." -> 你的问题是上下文为空。
- "Your fragment isn't attached to any." -> 你的片段没有附加到任何地方。
- "In this case, what you probably want to do is update any persisted state (if any) and skip updating the UI." -> 在这种情况下,你可能想要做的是更新任何已保存的状态(如果有的话)并跳过更新用户界面。
- "Also, if you're inflating your UI normally on an executor that then posts to a handler - stop." -> 另外,如果你通常在执行程序上正常膨胀你的用户界面,然后将其发布到处理程序 - 停止这样做。
- "That's not how it works." -> 这不是它的工作方式。
- "The inflation should happen in the onCreateView function." -> 膨胀应该发生在onCreateView函数中。
- "You can fill in the views like that, but you would NEVER inflate them like that." -> 你可以像那样填充视图,但绝不应该像那样膨胀它们。
英文:
You wouldn't do this at all. If you don't have a view, reinflating it isn't going to do what you expect. It would, at best, create a new set of views that are in memory only and not displayed on the screen. In other words it would be pointless.
Also, that's not what your problem is. You problem is that the context is null. Your fragment isn't attached to any. In this case, what you probably want to do is update any persisted state (if any) and skip updating the UI.
Also, if you're inflating your UI normally on an excutor that then posts to a handler- stop. THat's not how it works. THe inflation should happen in the onCreateView function. You can fill in the views like that, but you would NEVER inflate them like that.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论