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
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.
protected void onCreate(Bundle savedInstanceState) {`
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.test_button);
button.setText("before");
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
button.setText("after");
}
}, 2000);
}
I think this solution will not cause a memory leak. According to the answer which gets most votes (How to pause / sleep thread or process in Android?),this will cause a memory leak. what do you think?
To avoid memory leak, handler need to have WeakReference to activity. You can do it like this
private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();
public static class MyRunnable implements Runnable {
private final WeakReference<Activity> mActivity;
public MyRunnable(Activity activity) {
mActivity = new WeakReference<>(activity);
}
#Override
public void run() {
Activity activity = mActivity.get();
if (activity != null) {
Button btn = (Button) activity.findViewById(R.id.button);
btn.setBackgroundResource(R.drawable.defaultcard);
}
}
}
private MyRunnable mRunnable = new MyRunnable(this);
public void onClick(View view) {
my_button.setBackgroundResource(R.drawable.icon);
// Execute the Runnable in 2 seconds
mHandler.postDelayed(mRunnable, 2000);
}
Same is also mentioned in https://stackoverflow.com/a/3039718/3812404
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.
I'm trying to code a timer for Android, but I'm getting problems with the handler. This line:
handler.post(new Runnable(){
...is triggering a NullPointerException. Why?
public class HomeFragment extends Fragment {
private int stunde,minute,sekunde;
private TextView textfield;
private Button buttonStart,buttonStop;
private Handler handler;
private boolean Running = true;
private Runnable runnable;
private Thread thread;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
buttonStart =(Button) rootView.findViewById(R.id.button1);
buttonStop = (Button) rootView.findViewById(R.id.button2);
buttonStop.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
thread.stop();
}
});
textfield = (TextView) rootView.findViewById(R.id.startZeit);
buttonStart.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
Running = true;
runnable = new Runnable(){
#Override
public void run() {
while(Running){
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
handler.post(new Runnable(){
#Override
public void run(){
sekunde+=1;
if(sekunde == 60){
minute +=1;
sekunde = 0;
}if(minute == 60){
stunde +=1;
}
textfield.setText(String.format("%02d",stunde)+" : "+String.format("%02d", minute)+" : "+String.format("%02d",sekunde));
}
});
}
}
};
thread = new Thread(runnable);
thread.start();
handler = new Handler();
}
});
return rootView;
}
}`
Your handler has to be instantiated with
handler = new Handler();
before it is used.
Your formatting is quite hard to read but you get a NPE if runnable or handler have not been initialised. Since you are providing a new runnable object, that means your handler has not been initialised.
I think you are creating your handler inside an onClickListener. Try initialising it in onCreate or towards the top of onCreateView for clarity.
Goodluck
Because you didn't initialized the handler object. So you are getting a NullPointerException on it.
You will need to do some thing like that:
Handler handler = new Handler();
Somewhere in your code before you can use this object.
I have the below singleton handler class
public class MyHandler
{
private static Handler handler;
private static boolean isRunning;
public static Handler getHandler(Runnable myRunnable)
{
if (handler == null)
{
initHandler(myRunnable);
}
return handler;
}
private static void initHandler(Runnable myRunnable)
{
handler = new Handler();
isRunning = true;
handler.postDelayed(myRunnable, 5000);
}
public static void reRunHandler(Runnable myRunnable)
{
isRunning = true;
handler.postDelayed(myRunnable, 45000);
}
public static void stopMyHandler()
{
isRunning = false;
handler.removeCallbacksAndMessages(null);
}
}
However, how can I update my UI from here ? As the runnables are inside my activity. Apparently I cannot use getHandleMessage to communicate with it.
If you need more code, how am I using this, I can share.
It's very simple:
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
//do whatever you want on the UI thread
}
});
Handle has functions for this purposes:
private final Handler handler = new Handler() {
public void handleMessage(Message msg) {
// here you can get data from Message and update your UI. runs in UI thread
}
};
If you will send message with data to your Handler use next code:
Message m = new Message();
Bundle b = new Bundle();
b.putInt("myNumber", 5); // for example
m.setData(b);
myHandler.sendMessage(m);