I was dealing recently with a question I'm not sure how to answer.
I wrote a code example for some AsyncTask that I want to perform. I read somewhere on the net that someone has implemented the AsyncTask and the Handler as inner classes and I wanted to scale that a little bit and make less coupling so I made separated class for those so I can reuse them with more than one Activity.
Because I had to do some different UI things on each Activity I decided to make those activities implement an interface so I can react to each event with same methods.
What I don't understand is why do I need the handler object that will handle the messaging for event occurrence? can't I just use the listeners observer pattern? and then the question that I asked my self and can't understand the answers around the web is what is the difference between my listener observer implementation and the handler object we get from Android.
Here is my code example:
Activity 1:
public class SomeActivity extends Activity implements MyListener{
MyAsyncTask myTask;
MyHandler handler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new MyHandler();
myTask = new MyAsyncTask(handler);
// initilize the activity views etc...
}
#Override
public void do1(){
// DO UI THINGS FOR ACTIVITY 1 IN A CALLBACK TO DO1 EVENT
}
#Override
public void do2(){
// DO UI THINGS FOR ACTIVITY 1 IN A CALLBACK TO DO2 EVENT
}
}
Activity 2:
public class OtherActivity extends Activity implements MyListener{
MyAsyncTask myTask;
MyHandler handler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new MyHandler();
myTask = new MyAsyncTask(handler);
// initilize the activity views etc...
}
#Override
public void do1(){
// DO UI THINGS FOR ACTIVITY 2 IN A CALLBACK TO DO1 EVENT
}
#Override
public void do2(){
// DO UI THINGS FOR ACTIVITY 2 IN A CALLBACK TO DO2 EVENT
}
}
Listener interface:
public interface MyListener{
void do1();
void do2();
}
AsyncTask implementation:
public class MyAsyncTask extends AsyncTask<Void,Void,String>{
private MyModel m;
public MyAsyncTask(Handler h){
m = new MyModel();
m.setHandler(h);
}
protected String doInBackground(Void... params) {
// do something in background with MyModel m
return null;
}
}
Handler implementation:
public class MyHandler extends Handler {
Vector<MyListener> listeners = new Vector<>();
#Override
public void handleMessage(Message msg) {
switch(msg.what){
case 1:
// do something for case 1
fireMethod1();
break;
case 2:
// do something for case 2
fireMethod2();
break;
}
}
public void registerListener(MyListener l){
listeners.add(l);
}
public void unregisterListener(MyListener l){
listeners.remove(l);
}
private void fireMethod1(){
for(MyListener l : listeners){
l.do1();
}
}
private void fireMethod2(){
for(MyListener l : listeners){
l.do2();
}
}
}
Some demo model I created:
public class MyModel{
private Handel h;
public MyModel(){
// at some point send message 1 or message 2 ...
}
public void setHandler(Handler h){
this.h = h;
}
private void sendMessage1(){
h.obtainMessage(1, null);
}
private void sendMessage2(){
h.obtainMessage(2, null);
}
}
if it is too hard to read the code let me know, and if you don't want to read the code please help me to answer what is the difference between Handler and listening to events with the observer pattern? are they pretty much different solutions for same problem? thanks!
what is the difference between Handler and listening to events with the observer pattern?
The difference is that when you use a listener you call a method synchronously on the same thread. When you use a Handler you synchronously add a message to the MessageQueue but it is handled only after those messages that are already in the queue.
For example, if you are using a UI handler and you already called finish() on the activity and then added your message, it will be inserted after onStop() and onDestroy(). You can't achieve this with a listener.
The advantage of handlers is that you just add messages to queues and you don't care about threading. You can easily add a message to the UI handler from the background thread. If you use a listener from the background thread, it will be called on a background thread synchronously.
are they pretty much different solutions for same problem?
No, they are not. Handlers help you to decouple android components which is critical for Android, I think. If you use listeners you will be relying on strong references only which in some cases is not possible because you might leak a memory.
Handler is UI-threaded component. Usage of simple listener may cause CalledFromWrongThreadException if you want to touch some UI.
AsyncTask although have onPreExecute, onPostExecute and onProgressUpdate, which are just methods, which are running on UI thread. doInBackground runs on separate thread
Related
I have a DialogFragment, with a public interface and a method to save the callback:
public class ChooseExportTypeDialogFragment extends DialogFragment{
public interface ExportTypeClickListener{
public void doPositiveClick(ExportTypeEnum exportType);
}
public void setOnConfirmClickListener( ExportTypeClickListener callback ){
this.callback = callback;
}
//...
}
I declare a DialogFragment like this:
final ChooseExportTypeDialogFragment chooseExportTypeDialogFragment = new ChooseExportTypeDialogFragment();
chooseExportTypeDialogFragment.setOnConfirmClickListener( new ExportTypeClickListener() {
#Override
public void doPositiveClick( ExportTypeEnum exportType ) {
toast.setText("Preparing Export...");
toast.show();
controller.onExport( adapter.getSelected() , exportType);
chooseExportTypeDialogFragment.dismiss();
}
} );
chooseExportTypeDialogFragment.show( controller.getSupportFragmentManager(), "notification" );
In the method onExport(), I perform a Thread.sleep(3000) (among other things).
It seems that when doPositiveClick() is invoked, the sleeping (which is in onExport()) occurs before toast.show(). I tried to put both operations (toast.show() and .onExport()) in synchronized blocks, hoping the sleep will start only after finishing toast.show(), but the same problem happens.
I also tried putting toast.show() in a thread, then .start() it and .join() it. And only then call onExport(). Same problem
How can I show the toast and only then let onExport() start?
Thread.sleep is rather problematic in Android espessially when performed in the UI thread because it can cause other elements (in your case Toast) to stop responding. An alternative to your problem is to execute your controller.onExport() logic inside a handler.
#Override
public void doPositiveClick( ExportTypeEnum exportType ){
toast.setText("Preparing Export...");
toast.show();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
controller.onExport( adapter.getSelected() , exportType);
}
}, 3500);
chooseExportTypeDialogFragment.dismiss();
}
You might also need to disable clickability of your Dialog buttons while waiting for the toast text to finish.
I'm doing some Android development and I have an object, which doing a specific task. When that task is done I need to inform my main method (Main Activity), which is constantly running, that the process has been finished and pass some information about it.
This may sound a bit unclear, so I'll give you an example:
Take a look at the setOnClickListener() method in Android:
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
//This method is called on click
#Override
public void onClick(View v) {
//The View is passed in an anonymous inner class
}
});
It waits for a button to be clicked and calls the onClick(View v) method. I am seeking to achieve the same code structure. How to do this?
You mentioned "process". If you are truly doing something in a different process, then you need to look at interprocess communications (IPC). Otherwise, you can use an interface:
Create a class called MyListener:
public interface MyListener {
void onComplete();
}
In your class that will notify your activity:
MyListener myListener;
public void setMyListener(MyListener myListener){
this.myListener = myListener;
}
Then, when you are ready to notify your main activity, call this line:
myListener.onComplete();
Last, in your MainActivity implement MyListener:
public class MyListener extends Activity implements MyListener {
///other stuff
#Override
public void onComplete(){
// here you are notified when onComplete it called
}
}
Hope this helps. Cheers.
This is exactly Listener pattern that you use with views in android. What you want to do is declare an interface in your class that's doing the job, and pass an instance of this interface. Raw example:
TaskDoer.java:
public class TaskDoer {
public interface OnTaskDoneListener {
void onDone(Data data);
}
public void doTask(OnTaskDoneListener listener) {
// do task...
listener.onDone(data);
}
}
Activity:
public void doTaskAndGetResult() {
new TaskDoer().doTask(new TaskDoer.OnTaskDoneListener() {
public void onDone(Data data) {
// do something
}
}
}
I'm trying to build an Android app which will repeatedly run some process every 10 mins.
As I found out Handlers are more reliable than timers or scheduling. So I'm going to develop my app using the Handlers using the given below codes.
I'm little bit concerned that the below codes will create separate Handlers at each time I start the app and keep them running parallel, may be since I'm creating the Handler on onCreate.
So what is the best way to keep only a single Handler runs in background at a time?
private Handler handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler(); // new handler
handler.postDelayed(runnable, 1000*60*10); // 10 mins int.
setContentView(R.layout.activity_pro__sms);
}
private Runnable runnable = new Runnable() {
#Override
public void run() {
/* my set of codes for repeated work */
foobar();
handler.postDelayed(this, 1000*60*10); // reschedule the handler
}
};
You can extend Application class and do your work in it.
public class App extends Application {
private Handler handler;
#Override
protected void onCreate() {
super.onCreate();
handler = new Handler(); // new handler
handler.postDelayed(runnable, 1000*60*10); // 10 mins int.
setContentView(R.layout.activity_pro__sms);
}
private Runnable runnable = new Runnable() {
#Override
public void run() {
/* my set of codes for repeated work */
foobar();
handler.postDelayed(this, 1000*60*10); // reschedule the handler
}
};
}
And declare your class in manifest:
<application android:name=".App">
Edited
But it will work only if your app is running, otherwise you can use AlarmManager.
I decided to answer my own question since I've found out how to do it right way. The Android way. First of all what I was trying to do and posted in the question is a wrong approach to my requirement. Now I'm posting this so someone else will not do it wrong way but the following way.
Android has few options for timing.
Timer-task -> runs while application is alive. best for short term timing. Resource usage is higher.
Handler -> runs while application is alive. But not suitable to used as a scheduler. (this is what I've asked and it's not the correct way to do that). Handlers are the best way to do something repeatedly till the app is killed.
Alarm-manager -> The best way to schedule something to happen in future even if the app is killed. (this is what I should apply for my app).
This is what I figured out. Correct me if I'm wrong.
first define an utility class
public abstract class HandlerPeriodRunnable implements Runnable {
private Handler periodHandler;
private int msPeriod;
public HandlerPeriodRunnable(Handler periodHandler, int msPeriod) {
this.periodHandler = periodHandler;
this.msPeriod = msPeriod;
}
#Override
public void run() {
periodRun();
if (msPeriod > 0) {
periodHandler.postDelayed(this, msPeriod);
}
}
abstract public void periodRun();
}
then use it
final Handler mUIHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUIHandler.postDelayed(new HandlerPeriodRunnable(mUIHandler, 1000) {
#Override
public void periodRun() {
}
}, 2000);
}
My application crashes after click the button, but the code executes properly.
public void makeLead(View v) throws Exception {
try {
RegisterTimer rt = new RegisterTimer();
rt.ma = this;
rt.execute(null);
} catch (Exception e) {
e.printStackTrace();
}
}
public void log(String msg)
{
final TextView tv = (TextView)findViewById(R.id.editText);
tv.append(msg);
}
private class RegisterTimer extends AsyncTask {
public MainActivity ma;
#Override
protected Object doInBackground(Object[] objects) {
ma.log("ausd");
return null;
}
}
makeLead is onClick event. Method ma.log generates an error but works properly (msg added to textEdit). When I delete ma.log, app doesn't crash. I have no logs in my AndroidStudio so I can't see error message. What's wrong ?
You can not touch the Views in a non UI Thread.
and you are appending text to TextView in a background Thread which is not allowed.
and I hope there is no problem with the initialization of MainActivity inside RegisterTimer as you are not creating the instance of Activity manually. You are in correct way with the initialization rt.ma = this. and why do you need AsyncTask just for changing the text of a TextView?
You cannot update ui from a doInbackground. Initializing ma is not required
Make AsyncTask an inner class of Activity and update ui in onPostExecute
or use interface as a callback to the activity
Edit:
To make it clear
Make asynctaks an inner class of activity. Declare textview as a instance variable. Return result in doInbackground
In Activity
TextView tv;
#Override
public void onCreate(Bundle savedInstancestate)
super.onCreate(savedInstancestate);
setContentView(R.layout.yourlayout);
tv = (TextView)findViewById(R.id.editText);
Then
#Override
protected String doInBackground(Void objects) {
// some background computation
return "ausd";
}
In onpostExecute
#Override
protected void onPostExecute(String result)
{
super.onPostExecute();
tv.append(result);
}
Also you need
private class RegisterTimer extends AsyncTask<Void,Void,String> { // args missing
As described by #Raghunandan you have not initialized ma.
next is you cannot access view in background thread.
if your thread class is inside of MainActivity class then you can use
runOnUiThread(new Runnable() {
#Override
public void run() {
ma.log("ausd");
}
});
inside doInBackground method to update view.
Your method log is public, you don't need to make an object of the MainActivity class to access it, instead you can call it directly. Also you need to add some template after your ASYNC task, if you want to pass some input to your background process, you are using ASYNC task in a wrong way.
If I want to send an event, e.g. OnClick, to an activity from a thread? Thanks.
The expected work flow is below:
public class HelloAndroid extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Crate threadA
Start threadA
}
public void OnSomeEvent() {
do something that changes the views in this activity;
}
private class ThreadA extends Thread {
public void run() {
do something ...
Send Some Event to Activity HelloAndroid.
}
}
You can always send a message from a thread to the activity, like that:
//this should be in your Activity class
private Handler SomeHandler = new Handler() {
public void handleMessage(Message msg) {
ReactOnMessage();
}
};
private class SomeThread implements Runnable {
public void run() {
doSomething();
SomeHandler.sendEmptyMessage(0);
}
}
You can also create message, which will contain some values.
You will have to use Handlers to update UI.
All UI related event have to executed from UI Thread.
http://developer.android.com/guide/appendix/faq/commontasks.html#threading
If I understand correctly, you want to call the method OnSomeEvent() of HelloAndroid from your inner ThreadA class, right?
If this is the case you could right:
private class ThreadA extends Thread {
public void run() {
HelloAndroid.this.OnSomeEvent();
}
}
or even simpler, just call OnSomeEvent() method directly.