(2016.3.15 Updated)
I encountered a strange problem last week and I'd like to discuss this with you.
Problem Scenarios:
There's a searchView in a AppCompatActivity. Whenever the text changed, the fragment within the Activity will be replaced through "getSupportFragmentManager().beginTransaction().replace(R.id.fragment_layout, fragment).commit();
".
In the fragment, there's a thread called SearchThread,which will be executed in the onActivityCreated()
. Getting to the point, there's a getActivity().runOnUiThread(new Runnable{...})
method in it. No matter what is in the "new Runnable()", a NullPointerException will occur when the text of searchView changes quickly, which results in the frequent reestablishment of this fragment.
Logcat:
03-15 20:12:25.912/cn.example.app E/AndroidRuntime: FATAL EXCEPTION: Thread-10820
Process: cn.example.app, PID: 31532
java.lang.NullPointerException
at cn.example.app.homepage.GymFragment$searchThread.run(GymFragment.java:257)
at java.lang.Thread.run(Thread.java:841)
But, if I use sendMessage(searchHandler.obtain...) instead of runOnUiThread, everything will go well!!
GymFragment.Java:
@Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
new Thread(new SearchThread()).start();
}
class SearchThread implements Runnable{
@Override
public void run() {
String s = "";
try {
Thread.sleep(4000);
} catch (Exception e) {;}
//searchHandler.sendMessage(searchHandler.obtainMessage(0, s));//Correct
getActivity().runOnUiThread(new Runnable() { //Throw NullPointer Exception or pool-1-thread-1 (with ExecutorService)
@Override
public void run() {
;
}
});
}
}
SearchActivity.Java:
class queryChangeListener implements SearchView.OnQueryTextListener{
...
@Override
public boolean onQueryTextChange(String newText) {
currentSearchTip = newText;
if (newText != null && newText.length() > 0) {
searchDelayed(newText);
}
return true;
}
}
private Handler searchHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg == null)
return;
String searchText = (String) msg.obj;
if (currentSearchTip != null && currentSearchTip.isEmpty()==false) {
GymFragment fragment = GymFragment.newInstance(searchText);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_layout, fragment).commit();
}
}
};
private ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10);
private String currentSearchTip;
public void searchDelayed(String newText) {
scheduledExecutor.schedule(new SearchThread(newText),500, TimeUnit.MILLISECONDS);
}
class SearchThread implements Runnable {
String newText;
public SearchThread(String newText){
this.newText = newText;
}
public void run() {
if (newText != null && newText.equals(currentSearchTip)) {
searchHandler.sendMessage(searchHandler.obtainMessage(0, newText));
}
}
}
Solutions:
1) Comment out"getActivity()...",use the commented "sendMessage(searchhandler.obtain..)" instead.
2) Before run getActivity().runOnUiThread(), judge getActivity()==null first.
2016.3.14:
[ Some things interesting:) ]
runOnUIThread(Runnable r)'s source code:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
In fact,runOnUiThread calls mHandler.post(Runnable r).
Let's see what's in post(Runnable r):
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
And in the getPostMessage(Runnable r):
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Then in the Message.obtain():
public final Message obtainMessage() {
return Message.obtain(this);
}
public static Message obtain(Message orig) {
Message m = obtain();
m.what = orig.what;
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = orig.obj;
m.replyTo = orig.replyTo;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
2.the Source Code of "sendMessage(Message msg)":
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
Here we can see,the implementation of sendMessage(Message msg) is almost equal to the one of post(Runnable r). The difference may be associated with getPostMessage(Runnable r).
So, briefly, what actually results in the correct execution of searchHandler.SendMessage(searchHandler.obtainMessage(0,str)), while results in the exception of getActivity().runOnUiThread(new Runnable{...}), when I simultaneously executed them repeatedly? Thanks very much!
I can't say for sure why your fragment is detached from the activity. But if you want to run that code regardless of your application state, you could simply use a Handler instead of runOnUiThread. Just make sure to create the Handler in the ui/main thread and all calls to post() will result in the Runnable running on the ui/main thread.