When using nested AsycTask in activity will handler reference remain after onDestroy - android

I have sample code which very simply does some heavy work and sends a message to a handler to update the UIThread. My concern is with the handler reference i am passing to the constructor. If my activity gets destroyed while the asncTask is still running will the handler reference not be null ?
public class SomeActivity extends Activity
{
private static final int UPDATE_BUTTON_TEXT = 1;
private static final SomeActivity me = null;
private static Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (me == null) return;
switch (msg.what) {
case UPDATE_BUTTON_TEXT:
Button btn = (Button) me.findViewById(R.id.someButton);
btn.setText((String) msg.obj);
}
}
};
private View.OnClickListener onClickListener = new View.OnClickListener() {
public void onClick(View view) {
new SomeLongRunningTask().execute();
}
};
private static class SomeLongRunningTask extends AsyncTask<Void, Void, Boolean> {
private Handler handler;
public SomeLongRunningTask(Handler handler) {
this.handler = handler;
}
#Override
protected Boolean doInBackground(Void... voids) {
try {
Thread.sleep(30000); // replace with some background logic
} catch (InterruptedException e) {}
return true;
}
#Override
protected void onPostExecute(Boolean aBoolean) {
//can the handler be null here if activity is destroyed ????
Message msg = handler.obtainMessage(UPDATE_BUTTON_TEXT);
msg.obj = "success"
handler.sendMessage(msg);
}
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final Button someButton = (Button) findViewById(R.id.someButton);
someButton.setOnClickListener(onClickListener);
}
#Override
protected void onStart() {
super.onStart();
me = this;
}
#Override
protected void onStop() {
me = null;
super.onStop();
}
}

Yes, the reference of the handler is going to be retained in memory until it has a reference count > 0.
I think you should use AsyncTask().onProgressUpdate for updating progress on UI, which does what you're trying to do.
EDIT
If you're updating ui in onPostExecute then you don't need to use onProgressUpdate(my apologies).
Just use an interface as a callback function like below:
private interface Callback {
void updateUI(String value);
}
private static class SomeLongRunningTask extends AsyncTask<Void, String, Boolean> {
private Callback mCallback;
public SomeLongRunningTask(Callback callback) {
mCallback = callback;
}
#Override
protected void onPostExecute(Boolean aBoolean) {
mCallback.updateUI("success");
}
}
// somewhere else...
Callback callback = new Callback() {
#Override
public void updateUI(String value) {
Button btn = (Button) me.findViewById(R.id.someButton);
btn.setText((String) msg.obj);
}
};
new SomeLongRunningTask(callback).execute();
Also it doesn't seem right to have a handler instance as a static variable. It will last until the class is unloaded.

As a rule, whenever I write an AsyncTask subclass, I use a pattern like this:
private WeakReference<Callback> mCallbackRef;
public MyAsyncTask(Callback callback) {
mCallbackRef = new WeakReference<>(callback);
}
#Override
protected void onPostExecute(Boolean aBoolean) {
if (mCallbackRef != null) {
Callback callback = mCallbackRef.get();
if (callback != null) {
callback.updateUI("success");
}
}
}

Related

What is the solution of "This Handler Class Should Be Static Or Leaks Might Occur"?

I am working on a Bluetooth communication project where I need to transfer data among devices.Upon receiving an InputStream I pass the data to the UI thread from the worker thread using the following code:-
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
// Send the obtained bytes to the UI activity.
Message readMsg = handler.obtainMessage(MessageConstants.MESSAGE_READ,numBytes, -1,mmBuffer);
readMsg.sendToTarget();
Below is my handler class:-
public Handler mHandler = new Handler() {
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg=new String(readBuf,0,msg.arg1);
TextView textView=findViewById(R.id.textview);
textView.setText(readMsg);
}
}
But This shows the following warning:
This Handler class should be static or leaks might occur(anonymous android.os.Handler).
I tried making the class static but then it gives the following error:-
Non-static method findViewById(int) can't be referenced from a static context.
What should I do to resolve this?
public MyHandler mHandler;
public static class MyHandler extends Handler {
WeakReference<TextView> mTextViewReference;
public MyHandler(TextView textView) {
mTextViewReference = new WeakReference<TextView>(textView);
}
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg = new String(readBuf,0,msg.arg1);
TextView textView = mTextViewReference.get();
if(textView != null) {
textView.setText(readMsg);
};
}
public void clear() {
mTextViewReference.clear();
mTextViewReference = null;
}
}
protected void onCreate(final Bundle savedInstanceState) {
....
mHandler = new MyHandler(findViewById(R.id.textView));
....
}
#Override
public void onDestroy() {
if(mHandler != null) {
mHandler.clear();
mHandler = null;
}
super.onDestroy();
}
EDIT
Fix above works fine if you just want to update one single TextView. However, very often, you need to take more actions and update more stuff (not only a single TextView). So, I think you can create a Interface that is invoked every time a message is received. Something like:
public class MyActivity extends Activity {
public MyHandler mHandler;
protected final void onCreate(final Bundle savedInstanceState) {
//....
mHandler = new MyHandler(new MyHandler.OnMessageReceivedListener() {
#Override
public void handleMessage(final String message) {
// Update the views as you with
}
});
//....
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.clear();
}
public static class MyHandler extends Handler {
WeakReference<OnMessageReceivedListener> mListenerReference;
public MyHandler(OnMessageReceivedListener listener) {
mListenerReference = new WeakReference<>(listener);
}
public synchronized void handleMessage(Message msg) {
byte[] readBuf=(byte[])msg.obj;
String readMsg = new String(readBuf,0,msg.arg1);
OnMessageReceivedListener listener = mListenerReference.get();
if(listener != null) {
listener.handleMessage(readMsg);
};
}
public void clear() {
mListenerReference.clear();
}
public interface OnMessageReceivedListener {
void handleMessage(String message);
}
}
}
You're not doing very heavy staff in your handleMessage part, so no need to extend Handler keep it simple and ligthweight; just add a callback instead. Create a callback in your Activity/Fragment:
private class MessageCallback implements Handler.Callback {
#Override
public boolean handleMessage(#NonNull Message message) {
// Here you can call any UI component you want
TextView textView=findViewById(R.id.textview);
textView.setText(readMsg);
return true;
}
}
Then call it as:
Handler handler = new Handler(getMainLooper(), new MessageCallback());
Message readMsg = handler.obtainMessage(what, arg1, arg2, object);
readMsg.sendToTarget();

Start activity from inside thread

INTRODUCTION
I have an activity that processes some functions. Inside this activity, the main process is one thread that makes the processing of these functions.When the processing is done, it should call to another activity to start another diferent process.
This is my thread inside the main activity:
CODE
private static void DetectionThread (byte[] data, int width, int height, final Context context) {
mData = data;
mWidth = width;
mHeight = height;
mThread = new Thread() {
#Override
public void run() {
try {
//MAKES THE PROCESSING
//If it's right, continues to next code...
MotionDetectionActivity.gameStarted = true;
gameLaunched = true;
return;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
processing.set(false);
/*HERE MUST INIT THE ACTIVITY WITH INTENT*/
if (MotionDetectionActivity.gameStarted == true && gameLaunched == true) {
gameLaunched = false;
Intent gameIntent = new Intent(context, GameActivity.class);
context.startActivity(gameIntent);
}
processing.set(false);
}
}
};
if (MotionDetectionActivity.gameStarted == false) {
mThread.start();
}
}
QUESTION
Well, the thing is that i'm not getting the desired result. When initializing the GameActivity, it is not showing this activity's layout, and there are some functionalities that are not initialized, f.e. I do this to initialize the TTS:
private static TextToSpeech tts;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
tts = new TextToSpeech(this, this);
//Iniside main method
tts.speak("Initializing...", TextToSpeech.QUEUE_ADD, null);
The thing is that it doesn't talk.
Use AsyncTask instead of Thread, and call the another activity in the onPostExecute method
public class MyAsync extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
//start the next activity here
}
#Override
protected Void doInBackground(Void... params) {
//your task goes here
return null;
}
}

Android Refresh TextView from Thread

I know there are already quite a number of discussions about this, but none of what I found could clear my confusion.
I'm using the Android SDK for the first time and my Java Skills are rather average.
I have the following Problem:
From my MainActivity - OnCreate() fct. I start a thread (Receiver), receiving data from a SocketStream. This thread shall refresh a TextView-Element on the GUI when new data was read from the stream.
What is a simple but proper way to do so? I read something about ASyncTask, but did not understand how to implement it.
public class MainActivity extends Activity
{
ExecutorService myExecutor;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("this is a test"); System.out.flush();
try
{
myExecutor = Executors.newCachedThreadPool();
myExecutor.execute(Receiver.getInstance());
}
catch (IOException e)
{
e.printStackTrace();
}
}
...
public class Receiver implements Runnable
{
[...]
public void run()
{
while (true)
{
//blocking system-io-call to read data from socket..
//extract information
// *** update textView *** ??
}
}
}
You can implement handler in GUI thread to change GUI (in MainActivity in your case):
public Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
//refresh textview
}
};
and than call it from another threads
activity.handler.sendEmptyMessage(what);
You can write your own constructor for Receiver:
public class Receiver implements Runnable
{
[...]
MainActivity activity;
public Receiver(MainActivity activity){
this.activity = activity;
}
public void run()
{
while (true)
{
//blocking system-io-call to read data from socket..
//extract information
// *** update textView *** ??
activity.handler.sendEmptyMessage(0);
}
}
}
You can use runOnUiThread
public class Receiver implements Runnable
{
[...]
public void run()
{
while (true)
{
//blocking system-io-call to read data from socket..
//extract information
runOnUiThread(new Runnable() {
public void run() {
// *** update textView *** ??
}
});
}
}
}
this is a example:
create Counter class :
public class Counter implements Runnable
{
private ICounterEvents listener;
public static Thread OBJ_THREAD = null;
public Counter()
{
OBJ_THREAD = new Thread(this);
}
public void setCountListener(ICounterEvents listener)
{
this.listener = listener;
}
public void start()
{
OBJ_THREAD.start();
}
#Override
public void run()
{
for(int i = 0; i < 100; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
Message msg = Message.obtain();
msg.obj = i;
this.handler.sendMessage(msg);
}
}
private Handler handler =
new Handler()
{
#Override
public void handleMessage(Message msg)
{
if(Counter.this.listener != null)
{
int value = (Integer)msg.obj;
Counter.this.listener.countChanged(value);
}
}
};
}
and create a interface class:
public interface ICounterEvents
{
public void countChanged(int value);
}
and than in your main layout create a textview and a button,
and use this code in onCreate method in MainActivity:
public class MainActivity extends Activity implements ICounterEvents, OnClickListener
{
private TextView txtCounter;
private Button btnStart;
private Counter counter;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.setupViews();
}
private void setupViews()
{
this.counter = new Counter();
this.counter.setCountListener(this);
this.txtCounter = (TextView)findViewById(R.id.txtCount);
this.btnStart = (Button)findViewById(R.id.btnStart);
this.btnStart.setOnClickListener(this);
}
#Override
public void onClick(View v)
{
this.counter.start();
}
public void countChanged(int value)
{
try
{
this.txtCounter.setText(value + "");
}
catch (Exception e)
{
}
}
}

how to make a asynctasc work and dismiss after sometime?

I have a asynctask and I want to make it to be canceled after some time, 60 sec for example.
I think I have to it inside a while statemant, but I dont know how to count the time.
Here is my idea:
public class ThreadWithAutoCancel extends AsyncTask<Void, Void, Void> {
public ThreadWithAutoCancel(int timeOut) {
WatchDog watchDog = new WatchDog(this);
watchDog.execute(timeOut);
}
#Override
protected Void doInBackground(Void... params) {
// Do the job
return null;
}
class WatchDog extends AsyncTask<Integer,Void,Void>{
private long startTime;
private AsyncTask task;
public WatchDog(AsyncTask taskToStop){
task = taskToStop;
}
#Override
protected void onPreExecute(){
startTime = System.currentTimeMillis()/1000;
}
#Override
protected Void doInBackground(Integer... params) {
while(System.currentTimeMillis()/1000 < startTime+params[0]){
}
task.cancel(true);
return null;
}
}
}
After starting the AsyncTask, hold a reference to it and call cancel on it 60 seconds later, perhaps on a UI Thread Handler. Inside your doInBackground method you will have to make sure you return if isCancelled returns true. I hope the following snippet will help:
public class MyActivity extends Activity {
private Handler mHandler;
private AsyncTask<?, ?, ?> mTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
}
#Override
protected void onPostResume() {
super.onPostResume();
mTask = new MyCustomTask();
mTask.execute(1, 2, 3);
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mTask.cancel();
}, 60L);
}
}
And inside your custom task:
public class MyCustomTask extends AsyncTask<Integer, Float, String> {
#Override
protected String doInBackground(Integer... params) {
String output = "";
for (Integer i : params) {
// Check status for each param
if (isCancelled()) {
return output;
}
...
}
}
#Override
protected void onCancelled(String result) {
// This bit runs on the UI thread
...
}
You can do this using handler. For example this code will show "Completed" on TextView with R.id.mytext after asynctask will execute for 60 seconds:
final int FINISH = 1;
Thread waitingThread;
MyAsyncTask myAsyncTask;
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == FINISH)
{
myAsyncTask.cancel(true);
((TextView) findViewById(R.id.mytext)).setText("Completed");
}
};
};
// ...
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
waitingThread = new Thread(new Runnable() {
#Override
public void run() {
try {
TimeUnit.SECONDS.sleep(60);
mHandler.sendEmptyMessage(FINISH);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
waitingThread.start();
}
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
while (true) {
// do something
}
}
}

Update ProgressDialog from Thread inside Service

i want to increment a progress dialog from a thread inside a service, i have really hard time doing that, this is my code please help me.
I tried many different ways including asyncTask (I had problem with context)
and tried with static functions but its not working properly,
I pretty new with android please explain me the problem here.
the activity
public class MainActivity extends Activity {
ProgressDialog progressBar;
private void showProgrssBar() {
progressBar.show();
}
private void dismissProgressBar() {
progressBar.dismiss();
}
private void increaseProgressBar(int total) {
progressBar.incrementProgressBy(total);
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createProgressBarDialog();
Intent n = new Intent(this, myService.class);
startService(n);
}
private void createProgressBarDialog()
{
progressBar = new ProgressDialog(this);
progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressBar.setMax(200);
progressBar.setMessage("Recieving bluetooth data");
progressBar.setCanceledOnTouchOutside(false);
}
the service:
public class myService extends Service
{
private myThread myThread;
Handler handler = new Handler()
{
#Override
public void handleMessage(android.os.Message msg)
{
int total = msg.getData().getInt("total");
if (total == -1)
{
dismissProgressBar();
}
else if (total == 0)
{
showProgrssBar();
}
else
{
increaseProgressBar(total);
}
}
};
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
myThread = new myThread(handler);
myThread.start();
return START_STICKY;
}
the thread
class myThread extends Thread
{
Handler h;
int numOfLinesToRead = 220;
int line = 0;
public myThread(Handler h)
{
this.h = h;
}
private void increaseProgressBarOnActivity(int i_MsgType)
{
Message msg = h.obtainMessage();
Bundle b = new Bundle();
b.putInt("total", i_MsgType);
msg.setData(b);
h.sendMessage(msg);
}
#Override
public void run() {
super.run();
int increase;
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
for (; line < 220; line++)
{
increase = (line*100/numOfLinesToRead);
if (increase != 0)
{
increaseProgressBarOnActivity(increase);
try
{
Thread.sleep(90);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
Despite you having already tried AsyncTask, I still would strongly recommend to use it.
Just take a look at the onProgressUpdate() method. It is made to update the UI from AsyncTask.
Here is an example of how it could look like:
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {
private ProgressDialog progressBar;
#Override
protected void onPreExecute(){
super.onPreExecute();
progressBar= new ProgressDialog(getApplicationContext());
progressBar.setMessage("Loading...");
progressBar.show();
}
protected Long doInBackground(String... params) {
long someLong;
// do something here with params
// the Integer variable is used for progress
publishProgress(i);
// call it for example while downloading a file
return someLong;
}
// this is called whenever you call puhlishProgress(Integer)
protected void onProgressUpdate(Integer... progress) {
progressBar.incrementProgressBy(progress[0]);
}
// the onPostexecute method receives the return type of doInBackGround()
protected void onPostExecute(Long result) {
// do something with the result
progressBar.dismiss();
}
}
You said your problem was getting the Context. Well: Service is a Context
So you could simply make the AsyncTask an inner class of your Service and then use its Context.

Categories

Resources