Android: create and manage a view inside a secondary thread - java

In my app I need to use a specific view (WebView) for use some javascript code in background. This mean the webview have to be offscreen. Initially I created this view inside the UI thread using runOnUiThread but I noted when I make some operation into UI (touch the screen for move some object) my background view seem to slow down and is not "resposnive" as required. From here the idea to "execute" this view in a secondary thread for make it independent. I don't know if this will be possible anyway. I started making this code:
class WebViewThread extends Thread
{
public WebView MyWebView = null;
public void run()
{
Looper.prepare();
MyWebView = new WebView(MainActivityContext);
Looper.loop();
}
}
The problem is if I try to "interact" with the view calling some method from UI thread I got a runtime error telling me that the view can be touched only by the same thread who create it. Unfortunately this thread is "locked" into loop and I have no idea regarding how to "inject" the interacting code inside this thread.
Now the question: my idea to create and manage a view inside a secondary thread can be done or is a really bad idea that can not work?

As to whether or not that's a good idea, someone more experienced is going to have to elaborate on that subject. For what you're trying to do, though, assuming that you're not touching any UI elements, you could try a handler to pass the code to your webview. See if this works:
Handler handler;
class WebViewThread extends Thread
{
public WebView MyWebView = null;
public void run()
{
Looper.prepare();
MyWebView = new WebView(MainActivityContext);
//the handler will run on the thread
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
//do stuff to my webview
}
};
Looper.loop();
}
}
and then you can make the handler do stuff with
handler.sendMessage(message);

Related

Android Thread explanation?

Sorry everyone this has been asked a few times but I just do not understand any of the answers because most are about timed UI updates. So I have a backgroundTasks thread that is called when my app first starts(Does network connections so..I think that's how you do it?)
public class MainActivity extends ActionBarActivity {
String data[][];
int arrayPosition = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runBackgroundTask();
}
Here is my thread..
public void runBackgroundTask(){
new Thread(new Runnable() {
public void run() {
data = pullSchedule(data);
updateUI(data,arrayPosition);
}
}).start();
}
All it does is call a method pullSchedule which updates a 2D array with a webcrawler. So my problem comes when I call the updateUI method which is also used by two buttons to cycle through the array data which work perfectly fine. It's just when the thread first runs if I try to update the UI I get an error.
public void upDateUI(String data[][], int arrayPosition){
TextView gameTime =(TextView)findViewById(R.id.txtDate);
//more but deleted to save space :)
}
I have researched why I cannot update the UI from the background thread but I don't understand how to fix this? I thought about putting that entire method into the runOnUiThread(new Runnable()but then I believe my data and arrayPosition have to be declared final because of an inner class..First Android app just lost. Thanks for any help.
you can use asyntask for this from which you can update ui
public myAsyn extends AsyncTask<Void,Void,Void>
{
#Override
protected String doInBackground(Void... params) {
data = pullSchedule(data);
return null;
}
#Override
protected void onPostExecute(String result) {
updateUI(data,arrayPosition);
}
}
doInBackground runs on seperate thread whereas onPostExecute runs on Ui thread therefore you can update UI from there.
You are on the right track. The reason your app is crashing is because you are attempting to update a UI element off of the UI thread. To avoid this, you can either do as RichS suggested, and use an AsyncTask which will execute onPostExecute() on the UI thread, or surround your updateUI() call in your background thread with runOnUiThread(new Runnable() {....

Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()

I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).

Using a progressdialog and handler

I'm working on an app that connect to a webpage to get some content. I want to show a progressdialog, but I think I'm doing something wrong.
This is my code:
final ProgressDialog myProgressDialog = ProgressDialog.show(WhoisBeyondActivity.this, "Wait...", "Fetching data...", true);
Handler handler=new Handler();
handler.post(new Runnable()
{
public void run()
{
try {
// code to execute
Thread.sleep(2000);
} catch (Exception e) {
}
myProgressDialog.dismiss();
}
});
The problem is that the progressdialog is only shown one second at the end of the operation I want to make. I think the progressdialog is only executing when I execute the dismiss() because it appears and dissapears quickly. Is like the progressdialog appears only to dissapear ... help me please!!! I have read a lot of tutorials, and I have try a lot of option, like THREAD instead of HANDLER, but it is not usefull for me, because I have to edit UI.
There's an excellent example and tutorial here:
http://www.helloandroid.com/tutorials/using-threads-and-progressdialog
That's what I used the first time I did a threaded dialog in Android, and I bookmarked it. Hopefully it helps.
The reason you are getting the described behaviour is that the post method will just execute the passed in runnable against the thread to which the Handler is attached. In your case this is the UI thread.
You are calling ProgressDialog.show(), which is asynchronous. This does not actually show the dialog as soon as the method returns, rather you have just requested that the UI display a dialog. You then immediately post a thread that sleeps for 2 seconds, which is added to the UI queue and blocks the UI from performing the dialog show. The UI then wakes from your sleep, shows the dialog then is dismissed.
You should perform any network operation in either a new Thread or in an AsyncTask. Have a look at these links for more details:
AsyncTask
Painless threading
Threading
Designing for responsiveness
Thread documentation
Handler documentation
You don't want to use a separate thread per-say. What you really want is an AsynTask. This will allow you to create the progress dialog and do the background processing right there in the task. Simple to write and easier to implement. If your refer to the link, what you need is actually really similar to your question. With a little tweaking, it should work just fine for you.
public class HelloActivity extends Activity {
protected static final String TAG = "HelloActivity";
ProgressDialog myProgressDialog;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//
showDialog(1);
final Handler handler=new Handler(){
#Override
public void handleMessage(Message msg) {
//update UI here depending on what message is received.
switch(msg.what){
case 0:
myProgressDialog.dismiss();
}
super.handleMessage(msg);
}
};
Thread t = new Thread(){
public void run(){
try {
// code to execute
Thread.sleep(5000);
} catch (Exception e) {
}
handler.sendEmptyMessage(0);//nothing to send
}
};
t.start();
}
#Override
protected Dialog onCreateDialog(int id) {
myProgressDialog = ProgressDialog.show(HelloActivity.this, "Wait...", "Fetching data...", true);
return myProgressDialog;
}
}

Android thread confusion

I have written a function to create a splash screen with a 5 second timeout for my app.
The code works fine, but when the timeout reaches zero and I want to redirect to my main activity, the app crashes with the following error:
Only the original thread that created a view hierarchy can touch its views.
So I looked around a bit and someone suggested nesting this inside my function. It seems like a good Idea, but now methods like sleep / stop won't work.
My code is below, I can provide more / explain more in details if it isn't clear enough just let me know. Thanks for the help.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showSplashScreen();
}
protected boolean _active = true;
protected int _splashTime = 5000; // Splash screen is 5 seconds
public void showSplashScreen() {
setContentView(R.layout.splash_layout);
// Thread splashThread = new Thread() {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
int waited = 0;
while (_active && (waited < _splashTime)) {
Thread.sleep(100);
if (_active) {
waited += 100;
}
}
} catch (InterruptedException e) {
// do nothing
} finally {
showApplication();
}
}
});
}
Probably not what you want to hear, but you should never put a splash screen on your mobile app. With the exception of games, when people use a mobile app they want to get in, do what ever it is they need to do, and get out. If you make that process take longer, people are just going to get frustrated with you app. You should probably reconsider just not using a splash screen.
This will perform sleep on the UI thread. That's never a good idea.
Why not something like this?
new Handler().postDelayed(new Runnable() {
public void run() {
// start application ...
}
}, _splashTime);
But this answer has a good point. Displaying a splash screen for 5 seconds can be very annoying.
I believe you want AsyncTask for this. The method called on completion of the task will be called on your UI thread, making modifying UI elements much easier.
Use a Handler to post an event to the UI thread that will remove the splash.
Code should be something like...
splash.show()
new Handler().postDelayed(
new Runnable() {
void run() {
splash.remove();
},
delayTime);
I suggest you to make new activity for your spalsh screen, show it in a regular way (with startActivityForResult) and place in it such code (in it, not in your main activity):
new Handler().postDelayed( new Runnable()
{
public void run()
{ finish(); }
}, 5000 );
Also you can handle in this new activity click events for giving opportunity to user to close it faster, tapping on it.

Update View at runtime in Android

The example is pretty straightforward: i want to let the user know about what the app is doing by just showing a text (canvas.drawText()). Then, my first message appears, but not the other ones. I mean, i have a "setText" method but it doesn't updates.
onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(splash); // splash is the view class
loadResources();
splash.setText("this");
boundWebService();
splash.setText("that"):
etc();
splash.setText("so on");
}
The view's text drawing works by doing just a drawText in onDraw();, so setText changes the text but doesn't show it.
Someone recommended me replacing the view with a SurfaceView, but it would be alot of trouble for just a couple of updates, SO... how the heck can i update the view dinamically at runtime?
It should be quite simple, just showing a text for say 2 seconds and then the main thread doing his stuff and then updating the text...
Thanks!
Update:
I tried implementing handler.onPost(), but is the same story all over again. Let me put you the code:
public class ThreadViewTestActivity extends Activity {
Thread t;
Splash splash;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
splash = new Splash(this);
t = new Thread(splash);
t.start();
splash.setTextow("OA");
try { Thread.sleep(4000); } catch (InterruptedException e) { }
splash.setTextow("LALA");
}
}
And:
public class Splash implements Runnable {
Activity activity;
final Handler myHandler = new Handler();
public Splash(Activity activity) {
this.activity=activity;
}
#Override
public void run() {
// TODO Auto-generated method stub
}
public synchronized void setTextow(final String textow) {
// Wrap DownloadTask into another Runnable to track the statistics
myHandler.post(new Runnable() {
#Override
public void run() {
TextView t = (TextView)activity.findViewById(R.id.testo);
t.setText(textow);
t.invalidate();
}
});
}
}
Although splash is in other thread, i put a sleep on the main thread, i use the handler to manage UI and everything, it doesn't changes a thing, it only shows the last update.
I haven't hit this yet, but I think the usual pattern is to do lengthy initialization in a background thread, and use Handler.post() to update the UI. See http://developer.android.com/reference/android/widget/ProgressBar.html for a different, but possibly related, example.
Also see this answer, especially the first paragraph:
The problem is most likely that you
are running the splash screen (some
sort of Dialog such as ProgressDialog
I assume) in the same thread as all
the work being done. This will keep
the view of the splash screen from
being updated, which can keep it from
even getting displayed to the screen.
You need to display the splash screen,
kick off an instance of AsyncTask to
go download all your data, then hide
the splash screen once the task is
complete.
Update (based on your update and your comment): You are not supposed to update the UI in any thread except the one where your Activity is created. Why is it impossible for you to load your resources in a background thread?
First: onCreate is executed on main UI thread of application so no UI updates until you leave it. Basically you need one thread to execute long running tasks and some mechanism to push updates into the UI.
Most usual approach is to extend AsyncTask see this link for further info
i suppose that your view is an extended view and you call onDraw for drawing the view, so, maybe the view isnĀ“t 'refresh' their state, so try this
onCreate(Bundle bundle) {
setContentView(splash); // splash is the view class
loadResources();
splash.setText("this");
splash.invalidate();
boundWebService();
splash.setText("that"):
splash.invalidate();
etc();
splash.setText("so on");
splash.invalidate();
}

Categories