I encountered an unusual NPE exception in Android with handler and background thread.
I know when the activiy or fragment on destroy, the ui widget will be destroied, so you must cancel all the pending message or runnable sent from the background.
For example, in the fragment callback onDestroyView, i call handler' s removeCallbacksAndMessages(null) to remove all the callbacks. But, in some case, the callback still got executed, but at that time the ui has been destroied, the NPE throws.
Below is the sample:
public class SampleFragment extends Fragment {
private Handler mHandler = new Handler() {
#Override public void handleMessage(Message msg) {
switch (msg.what) {
// do your job
}
}
};
#Nullable #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// create some ui
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new Thread(new Runnable() {
#Override public void run() {
// after a long running work
mHandler.post(new Runnable() {
#Override public void run() {
// OOPS, the ui widget may null!
}
});
}
}).start();
}
#Override public void onDestroyView() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroyView();
}
}
I notice at some point, in the onDestroyView, the handler has been remove all the pendding message or runnable which will attach to the ui thread, and the the view set to be null to be gc. But the thread may still in the running state, and the handler will post the runnable, but the view is null.
The simple way is to check null from fragment' s getView() in the runnable, but if all of this situation needs to check null, that would be tedious.
Is there any nice approach to slove this kind of problem?
Thx in advance.
Related
I have a fragment that stems off the main activity. I am trying to have a textbox update with the users GPS location as they move around. I currently have it so every time you resume the fragment it updates, but I would like it to happen automatically every 10 seconds or so.
I am currently attempting to use runOnUiThread, which didn't cause my app to crash but didn't seem to do anything.
Within the fragment:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView newText = getView().findViewById(R.id.wText);
newText.setText(getStringCoordinates);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
newText.setText(getStringCoordinates);
}
});
}
Try using a handler, something like this should work
private Handler myHandler;
private static final int DELAY = 10000;
#Override
protected void onResume() {
super.onResume();
myHandler = new Handler(Looper.getMainLooper());
checkAgain();
}
private void checkAgain() {
myHandler.postDelayed(()-> checkGps(),DELAY);
}
private void checkGps() {
//do stuff here
checkAgain();
}
#Override
protected void onStop() {
super.onStop();
myHandler.removeCallbacksAndMessages(null);
myHandler = null;
}
basically it sends a message to the main thread every 10 seconds to check gps
the code may be wrong cause I'm writing it off the top of my head, but it should give you a good start
Maybe this is working
public class c_Thread_Update_Fragment extends Thread {
int i =0;
c_Thread_Update_Fragment(FragmentManager fm, ViewPager vp)
{
this.fragmentManager =fm;
this.mViewpager =vp;
}
#Override
public void run() {
while(true)
{
f.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
yourfragmentclass.updateData(i);
}
});
i++;
sleep(1000);
}
}
public static void setFragment(Fragment f){
f =f;
}
}
Implement a public static void update (xxx){} in yourfragmentclass
Use setFragment(f) in your Fragment adapterclass and pass the current fragment.
I have already had a look at the question here: Android Runnable runs faster after Resume
but it does not solve my problem.
I have a runnable in a fragment, that changes the image every 5 seconds. When I first move to the fragment, it is the first fragment in the activity, it is fine: the images change every 5 seconds.
However, if I move to another fragment and come back, the runnable runs faster, so some images are changed every 2 seconds.
I have tried all the questions I could find on the topic, and still I am not able to do this right, so help will be very much appreciated.
Here is my code:
public class Home_Fragment extends Fragment implements RadioGroup.OnCheckedChangeListener {
Runnable wowUpdateResults;
Handler wowHandler = new Handler();
int wowdelay =0 ;
int wowperiod = 5000;
Timer wowtimer = new Timer();
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.wow_layout, container, false);
....
wowUpdateResults = new Runnable() {
public void run() {
WowAnimateandSlideShow();
}
};
wowtimer.scheduleAtFixedRate(new TimerTask() {
public void run() {
System.out.println("I am on scheduled at fixed rate");
wowHandler.post(wowUpdateResults);
}
}, wowdelay, wowperiod);
return view;
}// end of onCreateView
private void WowAnimateandSlideShow() {
... //code to get the right image to be shown
Animation rotateimage2 = AnimationUtils.loadAnimation(getActivity().getBaseContext(), R.anim.fade_in);
wowprayer.startAnimation(rotateimage2);
}
public void onPause(){
super.onPause();
if (wowHandler != null){
System.out.println("I am on the onPause, removing calls "+wowHandler);
wowHandler.removeCallbacksAndMessages(wowUpdateResults);
}
}
public void onResume(){
super.onResume();
wowHandler.postDelayed(wowUpdateResults, 5000); // from the above mentioned question
}
Also, the removeCallbacksAndMessages does not seem to be working, when the user moves away from this fragment there are still calls to the runnable.
Any help is much appreciated.
Thanks.
It could be cause Timer is not stopped. Cancel theTimer in your onPost method:
public void onPause(){
super.onPause();
if (wowHandler != null){
System.out.println("I am on the onPause, removing calls "+wowHandler);
wowHandler.removeCallbacksAndMessages(wowUpdateResults);
}
if(wowTimer != null){
wowTimer.cancel();
}
}
I have a Fragment which sets up a ListView and creates a Handler to update the Listview periodically. However, it looks like the Handler still runs after the Fragment has been destroyed.
The following is the code.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//boilerplate code
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
assignAdapter();
handler.postDelayed(this, 15000);
}
});
return v;
}
Updating the ListView after the destruction of the Fragment causes the app to crash. How can I cause the Handler to stop as the Fragment gets destroyed? I would also like to know what effects if any pausing the app has on the Handler as well.
You need to implement handler like this
private Handler myHandler;
private Runnable myRunnable = new Runnable() {
#Override
public void run() {
//Do Something
}
};
#Override
public void onDestroy () {
mHandler.removeCallbacks(myRunnable);
super.onDestroy ();
}
You need to store a reference to your handler and runnable in the fragment, and then when the fragment is destroyed you need to remove callbacks from the handler passing in the runnable.
private Handler mHandler;
private Runnable mRunnable;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//boilerplate code
mRunnable = new Runnable() {
#Override
public void run() {
assignAdapter();
handler.postDelayed(this, 15000);
}
};
mHandler = new Handler(mRunnable);
mHandler.post();
return v;
}
#Override
public void onDestroy() {
mHandler.removeCallbacks(mRunnable);
super.onDestroy();
}
Another way of stopping the handler with the use of WeakReference to the fragment:
static final class UpdateUIRunnable implements Runnable {
final WeakReference<RouteGuideFragment> weakRefToParent;
final Handler handler;
public UpdateUIRunnable(RouteGuideFragment fragment, Handler handler) {
weakRefToParent = new WeakReference<RouteGuideFragment>(fragment);
this.handler = handler;
}
public void scheduleNextRun() {
handler.postDelayed(this, INTERVAL_TO_REDRAW_UI);
}
#Override
public void run() {
RouteGuideFragment fragment = weakRefToParent.get();
if (fragment == null || fragment.hasBeenDestroyed()) {
Log.d("UIUpdateRunnable", "Killing updater -> fragment has been destroyed.");
return;
}
if (fragment.adapter != null) {
try {
fragment.adapter.forceUpdate();
} finally {
// schedule again
this.scheduleNextRun();
}
}
}
}
where fragment.hasBeenDestroyed() is simply a getter for mDestroyed property of a fragment:
#Override
public void onDestroy() {
super.onDestroy();
mDestroyed = true;
}
Someone posted another question similar and the problem is due to a bug in the ChildFragmentManager. Basically, the ChildFragmentManager ends up with a broken internal state when it is detached from the Activity. Have a look at the original answer here
I have a Fragment in which I am running an animation code in run method of a new Thread. Since the thread runs for too long, I want to stop it when user moves away from the fragment, by pressing back button or clicking on the other menu item.
I tried using thread.stop, thread.destroy, wait, suspend, etc in the onPause() method but nothing is helping, it says the UnsupportedOperation or Object is not locked. And crashing the app.
The reason for this requirement is if I don't kill the thread, then when trying to exit from the app it does not close the app gracefully and crashes on pressing the back button multiple times.
Please suggest a good way to start and kill a thread on creation and switching of fragment respectively.
public class SearchFragment extends Fragment {
private Thread mAnimationThread;
public SearchFragment(){}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.search_anim, container, false);
mAnimationThread = new Thread() {
//some code here
};
return rootView;
}
#Override
public void onResume() {
super.onResume();
Log.d("anim", "in resume");
Toast.makeText(getActivity().getApplicationContext() , "Resumed", Toast.LENGTH_LONG).show();
mAnimationThread.start();
} #Override
public void onPause() {
super.onPause();
Log.d("anim", "in pause");
Toast.makeText(getActivity().getApplicationContext(), "Paused",
Toast.LENGTH_LONG).show();
mAnimationThread.suspend();
}}
See to this sample code:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThread());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
class MyThread implements Runnable {
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Working.");
}
System.out.println("Interrupted.");
}
}
Thread.stop() is highly discouraged since a time as it might terminate your Thread in an unsafe way (which you're probably experiencing). Probably the best way you can achieve this is having a boolean controller within your Thread, so while it is true, you'd keep processing the loading, and once it gets false (which you would control with your Fragment callback), it would return from the Thread and it just stops loading.
boolean doRun = true;
new Thread(
new Runnable() {
#Override
public void run() {
while (doRun) {
// Do whatever you need, and once this variable gets false, the Thread would exit
...
}
}
}).start();
override the on backpressed method and stop the thread there only.
I am developing a serialport application for knx modules in android. I can send and recieve commends to knx modulde.
I want to change ui(for ex. button properties) when a message recieved from serialport. I tried it with handlers but i havent be able to change ui. help me plss.
#Override public void OnSerialsData(final byte[] buffer, final int
size) { .... }
its my serialport listener function calling insine ReadThread. This thread is starting in differend package from my activity. I want to send a message in this method to main activity.
You can use Activity.runOnUiThread() to communicate with UI thread. Read more about Processes and Threads, especially about worker threads.
For example within your OnSerialsData, you can call
mActivity.runOnUiThread(new Runnable() {
public void run() {
mActivity.mButton.setText("message arrived!");
}
}
first you have to create a static handler inside your main activity:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public static Handler myHandler = new Handler(){
#Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
Log.e("Test", msg.getData().getCharSequence("MAINLIST").toString());
}
};
}
then in your socket class:
public void OnSerialsData(final byte[] buffer, final int size) {
Message msg = MainActivity.myHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putCharSequence("MAINLIST", "IS_OK");
msg.setData(bundle);
MainActivity.myHandler.sendMessage(msg);
}
but you have to ensure that your handler must be created before you call OnSerialsData method.
I hope this help.
just extending #auselen answer.
Create on your activity the following:
public void messageReceived(final String msg){
runOnUiThread(new Runnable() {
#Override
public void run() {
// Put here your code to update the UI
}
});
}
and then you can call this from any class that have a reference to your activity.
If the class does not have a reference to the activity, then you should pass the reference to it.