I'm using WebView with javascript interface and sometimes when I call loadUrl on the webView, mWebView.getContext() return null.
How can a view have no Context ? Why ?
Is a view whitout context still used or reusable ?
What should I do when my view did not have a context ?
Most important, if the view has no context, will mWebView.post(new Runnable() { ... be executed ?
Is this code relevant ?
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
mWebView.loadUrl("javascript:...");
} else {
mWebView.post(new Runnable() {
public void run() {
mWebView.loadUrl("javascript:...");
}
});
}
2 common reasons of a null context to a view:
You're trying to get the context in the callback of an asynchronous (handler, network call) call but the activity/fragment is gone because of another action of the user (ex: pressed back). Therefore the view is detached and has no context anymore.
You have a memory leak somewhere and two or more instances of your activity and view hierarchy. Then something happen in the instance you're not refering to anymore but the view has lost the link to its context.
Regarding the handler.
I am not sure if the view has its own handler or if it uses the handler of the Activity it is attached to, you'd probably have to read the source to find out. However the question is not really relevant: if the view has no context, you have a bigger problem: it's not on the screen.
Regarding whether the code in 5. is relevant, you would need to answer that questions: Why don't you know on which thread your code is running?
When you know on which thread you are, and if it makes sense to you not to be on the main thread, then using a handler is a valid way to execute your code on the main. As well as Activity.runOnUiThread()
Just remember that a Handler's lifecycle is not tied to the activity. So you should clear the queue of messages and runnables when your activity/fragment pauses
Related
I am using kotlin view binding in my fragment. In some cases app crashes with IllegalStateException & view as null, I am accessing it in a runnable which is called using a handler with a 1.5sec delay.
numberRunnable = Runnable {
if (mobileView.text.toString().isNotEmpty() && Utility.isMobileNumberValid(mobileView.text.toString())) {
presenter.getCustomerDetails(Utility.getServerAcceptableContactNumber(mobileView.text.toString()))
}
}
mobileView is null
Handler code:
handler.postDelayed(numberRunnable, 1500)
I am aware of one possibility to check if isAdded in my fragment, but since I cannot replicate the bug I am not sure if its the problem.
The action is likely running after the user leaves the Fragment and onDestroy() is called. In that state, there will be no View instances in the Fragment.
A simple workaround would be to create a global var to check your Fragment's created state. Set it to true in onViewCreated() and false in onDestroyView() (before the super call). Then check that value inside the Runnable before executing your logic.
A better solution (although this is subject to race conditions and needs every Runnable being assigned to a global variable) might be to use the Handler.removeCallbacks() method and pass all your Runnables.
override fun onDestroyView() {
handler.removeCallbacks(numberRunnable)
}
Yet another possibility is to simply say the View is nullable:
mobileView?.let {
//Your stuff goes in here
//You can reference mobileView using "it"
}
You cannot assume that after 1.5s the views are still attached to the view hierarchy.
Add handler.removeCallbacks(numberRunnable) to your onStop() life-cycle callback to remove the numberRunnable when the fragment is not active anymore.
Also ask yourself the question of why you need to have the delay.
If I leave a thread running when I quit my android app, can I get access to that thread when the app is restarted? I know that the thread is still associated with my app because I can kill it by going to settings-apps-force stop.
more details: my app connects to a device via bluetooth. when i rotate the tablet, it restarts the app, but if i don't stop all the threads, the old thread reconnects to the device and the app is not able to connect with a new thread.
I have fixed the basic problem by not allowing the app screen to rotate, and by killing the connect thread onDestroy(). but I would like to know how to re-connect with that sort of zombie thread just out of curiosity.
I can see threads that I don't recognize in Thread.enumerate(), but I don't know how to get access to those threads, other than seeing the name and their state.
The way I deal with this in my apps is to override an Activity's onRetainCustomNonConfigurationInstance() method, which allows you to retain an object through the restart that happens when the screen is rotated. Here's how I implement it.
I have an AsyncTask that performs a web request. The AsyncTask is in a separate file, and takes a reference to the calling Activity as a listener for some callbacks I have implemented. So the constructor for my web request AsyncTask is something like this:
private Callbacks listener;
public WebRequest(Callbacks listener) {
this.listener = listener;
}
I implement onRetainCustomNonConfigurationInstance() in my Activity like this:
#Override
public Object onRetainCustomNonConfigurationInstance() {
if(webRequest != null) {
webRequest.detachFromActivity();
return webRequest;
} else {
return null;
}
}
Now, when my screen is rotated, the Activity restarts, and if my AsyncTask is running, it will save a reference to it here. Notice that I also "detach" my task from this current Activity, which will now be destroyed. I accomplish this in my task by just making the listener (which is the current Activity) null. Like this:
public void detachFromActivity() {
listener = null;
}
Now when the Activity restarts, in onCreate(), I check to see if there was a retained reference to my running thread by calling getLastCustomNonConfigurationInstance() like this:
Object retainedRequest = getLastCustomNonConfigurationInstance();
if(retainedRequest != null) {
if(retainedRequest instanceof WebRequest) {
webRequest = (WebRequest) retainedRequest;
webRequest.setListener(this);
}
}
Since the reference to my running thread is passed as an Object, I need to retrieve it as an Object, then check if it's an instance of my AsyncTask, then cast it if it is.
The last step is to "reconnect" the callbacks to this NEW Activity, which was just created, so the task knows where to send the results. I use the setListener(this) method to do it in my task, like this:
public void setListener(Callbacks listener) {
this.listener = listener;
}
Now I can re-attach a reference to an old thread with a newly re-created Activity. You may not be using an AsyncTask, but the concept is the same and should work for any Thread, or any object you want, really.
Hope this helps!
Im not sure on your question, but what you are doing is kinda wrong. Screen rotation are UI changes and they should not affect your other code.
Check this answer for some guidance- http://stackoverflow.com/questions/5913130/dont-reload-application-when-orientation-changes
PS: NoChinDeluxes answer is also good for decoupling UI with other elements
The basic problem, as you have discovered, is that you have implemented your app in such a way that your bluetooth connection is logically bound to an Activity (i.e. the Activity is responsible for keeping track of the thread handling bluetooth activity).
To have the bluetooth connection reference survive a rotation, you will need to decouple it from the Activity. There are a number of ways to do this, depending on exactly what your requirements are.
You could, for instance, implement the bluetooth code as a Service.
There are other ways as well - for instance, take a look at Activity restart on rotation Android
I am re-submitting this question, as I don't think my last one really went into the problem I was having.
So I have a thread that returns and then uses a handler to update the UI as the following:
public void completeSignIn(final boolean success, final String error) {
Log.d(Constants.LogTag, "Finalising Sign In...");
final Looper mainLooper = Looper.getMainLooper();
final Handler mainHandler = new Handler(mainLooper);
mainHandler.post(new Runnable() {
#Override
public void run() {
if (success) {
TextView tv = (TextView) getActivity().findViewById(R.id.register);
tv.setText("SIGNED IN!!");
} else if (!success) {
}
}
});
}
The problem I am having is that because the thread could return at any point after it is done querying the server, there is not gaurantee that getActivity() will return the Activity. I have found that if I rotate my device as the thread is about to return, this bit of code can be called inbetween the activities destroy/create cycle. I am unsure if it is important that I am using a fragment here, but I don't think there is any harm in using the parent activity to update views?
So I am unsure how I could force the handler to wait until the activity is created - is this possible, or is thee a standard way of dealing with this? As I don't see this in other apps I have tested.
UPDATE
I put some logs in my fragment and managed to get the following to illustrate my issue:
07-09 22:17:15.164 25435-25435/? D/Kevins_Tag﹕ Detaching Activity...
07-09 22:17:15.234 25435-26702/? D/Kevins_Tag﹕ Signing in...
07-09 22:17:15.234 25435-26702/? E/Kevins_Tag﹕ Activity is null
07-09 22:17:15.234 25435-26687/? D/Kevins_Tag﹕ Finalising Sign In...
07-09 22:17:15.284 25435-25435/? D/Kevins_Tag﹕ Attaching Activity...
07-09 22:17:15.284 25435-25435/? D/Kevins_Tag﹕ Activity Exists
As you can see, the thread is calling the UI in between detach and attach...
I don't know for sure if this could solve your problem, but one thing I would try is check for an existing activity before do anything, like this:
//any code
Activity activity = getActivity();
if(activity != null) {
if (success) {
TextView tv = (TextView) activity.findViewById(R.id.register);
tv.setText("SIGNED IN!!");
} else if (!success) {
}
}
//any code
As you explain in your question, method completeSignIn() is in one of your fragments. The Runnable that completeSignIn() contains is an inner class of the fragment, and as such, holds a reference to the fragment. That is how the code in the Runnable can call fragment method getActivity()--the call is made using the "hidden" fragment reference.
You correctly observe that during a configuration change, completeSignIn() can get called after the fragment has been detached from the activity, resulting in a null activity reference. You ask:
I am unsure how I could force the handler to wait until the activity
is created
That is not possible in this case. However, even if it were, it wouldn't help. The fragment that completeSignIn() has a reference to is dead. It was detached and destroyed and will eventually be garbage collected. When your activity is recreated, the old fragment is not used--a new fragment is constructed and goes through the creation lifecyle steps using the saved state of the old fragment.
It's difficult to suggest an alternative approach without knowing how the overall server sign-in status and display is managed. One option would be to maintain the status in a singleton in addition to updating the sign-in view when sign-in completed. For configuration changes, where the view may not have been updated because it was inaccessible, the activity would be responsible for updating the view when the activity is recreated, using the status from the singleton.
After an AsyncTask finishes, I usually do one of the following;
Call a method on a callback object/interface that I define in the activity and pass to My AsyncTask subclass constructor.
Provide the AsyncTask with a Handler and call myHandler.sendMessage() when the task is complete.
What I want to ask is what is considered best practice after the AsyncTask is complete. If the user has pressed the home button while the task is processing, the activity is no longer in the foregreound. As soon as the app tries some UI operation, as a response to the task beeing completed, the The OS throws a WindowManager$BadTokenException.
I can of course surround my UI code with a catch for BadTokenException. That seems hacky. Is there any way to tell if the activity is in front?
Maybe you will not like my answer, but I consider AsyncTask broken (for reasons like this one).
Edit: My initial answer recommended to use an IntentService and broadcast the result. This is as inefficient as sending a letter to yourself.
You can use an AsyncTaskLoader which works around the problems of AsyncTask, but the API of AsyncTaskLoader is far from perfect as well. In particular, you must ensure that loader id is unique, and be aware that results are cached for the id, not for the arguments. Also, the propagation of exception is as broken as with AsyncTask.
A more modern and safer way to approach the problem is to use Guava future.
It means you are using some where the context that is not appropriate. To clear you doubt about the exception see this Link.
Bad Token Exception
You can check if the activity is active or not. I usually make my AsyncTask subclass as static (to avoid memory leak) so I pass a reference of the activity (wrapped on a WeakReference, again to avoid memory leaks).
When onPostExecute is executing I do the necessary checks when using WeakReferences plus call Activity.isFinishing() for the activity, so I can check the activity is not in process of being destroy, to avoid execute UI changes on a dying Activity.
Define an object of your activity in your onStart() as a static member -
private static MyActivity mActivity = null;
public void onStart() {
mActivity = this;
}
public void onDestroy() {
mActivity = null;
}
Then on your AsyncTask method, do something like:
if (mActivity != null) {
mActivity.doSomething(); //make sure to use static member
}
I have a semi-complicated problem and hoping that someone here will be able to help me.
On a click event I create a thread and start a long-running operation based on this method. After the long-running task is completed, it does a callback to another method, which does a post to the handler:
#Override
public void contentSearchModelChanged(Model_ContentSearch csm, ArrayList<Class_Reminder> newRemindersList) {
remindersList = newRemindersList;
mHandler.post(mUpdateDisplayRunnable);
}
Which calls a Runnable:
// post this to the Handler when the background thread completes
private final Runnable mUpdateDisplayRunnable = new Runnable() {
public void run() {
updateDisplay();
}
};
Finally, here is what my updateDisplay() method is doing:
private void updateDisplay() {
if (csModel.getState() != Model_ContentSearch.State.RUNNING) {
if(remindersList != null && remindersList.size() > 0){
r_adapter = new ReminderAdapater(Activity_ContentSearch.this, remindersList, thisListView);
thisListView.setAdapter(r_adapter);
r_adapter.notifyDataSetChanged();
}
}
}
This works beautifully when I do this normally. However, if I change the orientation while the long-running operation is running, it doesn't work. It does make the callback properly, and the remindersList does have items in it. But when it gets to this line:
r_adapter.notifyDataSetChanged();
Nothing happens. The odd thing is, if I do another submit and have it run the whole process again (without changing orientation), it actually updates the view twice, once for the previous submit and again for the next. So the view updates once with the results of the first submit, then again with the results of the second submit a second later. So the adapater DID get the data, it just isn't refreshing the view.
I know this has something to do with the orientation change, but I can't for the life of me figure out why. Can anyone help? Or, can anyone suggest an alternative method of handling threads with orientation changes?
Bara
The problem is that when you change orientations a new activity is spun up from the beginning (onCreate). Your long running process has a handle to the old (no longer visible) activity. You are properly updating the old activity but since it isn't on screen anymore, you don't see it.
This is not an easy problem to fix. There is a library out there that may help you though. It is called DroidFu. Here is a blog post that (much more accurately than I) describes the root cause of what you are seeing and how the DroidFu library combats it: http://brainflush.wordpress.com/2009/11/16/introducing-droid-fu-for-android-betteractivity-betterservice-and-betterasynctask/
Edit: (Adding code for tracking active activity)
In your application class add this:
private Activity _activeActivity;
public void setActiveActivity(Activity activity) {
_activeActivity = activity;
}
public Activity getActiveActivity() {
return _activeActivity;
}
In your Activities, add this:
#Override
public void onResume() {
super.onResume();
((MyApplicationClassName)getApplication()).setActiveActivity(this);
}
Now you can get the active activity by calling MyApplicationClassName.getActiveActivity();
This is not how DroidFu does it. DroidFu sets the active activity in onCreate but I don't feel that is very robust.
I had a similar problem, having a time consuming thread sendEmptyMessage to a handler, which in turn called notifyDataSetChanged on a ListAdapter. It worked fine until I changed orientation.
I solved it by declaring a second handler in the UI thread and make the first handler sendEmptyMessage to this handler, which in turn called notifyDataSetChanged on the ListAdapter. And having the ListAdapter declared as static.
I'm a newbie so I don't know if it is an ugly solution, but it worked for me...
From Jere.Jones description I would assume this works as: The long running process sendEmptyMessage to the handle from the old Activity, which in turn sendEmptyMessage to the handle in the new Activity.