简体   繁体   中英

How can I tell if my context is still valid?

I'm working with a fairly common situation right now - download some data over the web, then update a view to display it. Clearly, I want to do the web download in the background, and then update the view on the main UI thread. Now looking at my code, I'm a little worried about my Activity and its UI elements being killed off before I update them. Here's the essence of what I have in mind:

Thread update = new Thread() {
    public void run() {
        final Data newData = requestData();                     
        if (newData != null) {
            post(new Runnable() {
                public void run() {
                    Toast.makeText(MyClass.this, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
};
update.start();

It seems possible that while I'm downloading data, the activity may be destroyed. What happens then? Will my thread continue to execute? Will I end up trying to access dead objects?

Usually I do this by AsycTask, but the work seemed simple enough this time to just inline the threads-launching-threads stuff. Will I make things any better by using an AsyncTask instead?

If your Context is an Activity , you can check if it is finishing or has finished with the isFinishing() method:

if ( context instanceof Activity ) {
    Activity activity = (Activity)context;
    if ( activity.isFinishing() ) {
        return;
    }
}
Toast.makeText(context, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();

What you really want to use is an AsyncTaskLoader . These are my new favorite classes in the Android API. I use them all the time and they were made to solve problems just like this. You won't have to worry about when to stop your download or anything like that. All the threading logic is taken care of for you, including telling the thread to stop if the activity has been closed. Just say what it is you want to do in the loadInBackground() method. Note that if you are developing for an API lower than 3.0, you can still access all the loaders via the Android Support Package .

If you use anonymous classes, they will have an internal reference to the outer class, so it's not like it becomes inaccessible all of a sudden because other references have been cleared. AsyncTask actually doesn't change anything, it uses similar mechanics for notifying about results.

You can use loaders , they are designed to be in sync with the activity lifecycle. They are available only since Android 3.0, but you can use support package to work with them on any device with 1.6 or later.

There is even a simpler solution, you can just use a boolean field which indicates whether activity has gone away. You should set this field in onPause() (or whenever you think you won't need the notifications anymore) and check for it when you show toast. You won't even have to use synchronization, since this field is confined to the main thread, so it's absolutely safe. By the way, if you change this field somewhere else than in onDestroy() , don't forget to add a statement which resets your field back in the counterpart method.

public class MyActivity extends Activity {
    private boolean activityDestroyed = false;

    @Override
    protected void onDestroy() {
        activityDestroyed = true;
    }

    private void updateData() {
        new Thread() {
            @Override
            public void run() {
                final Data newData = requestData();                     
                if (newData == null) return;                                              

                runOnUiThread(new Runnable() {
                    public void run() {
                        if (activityDestroyed) return;
                        Toast.makeText(MyActivity.this, "Blah",
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }.start();
    }
}

I usually use Weak Reference to avoid leaking context in views

Weak Reference for Context

private var mContext: WeakReference<Context?>? = null

Assign Context

mContext = WeakReference(appContext)

Get Context

mContext .get()

Validate Context

  if (mContext?.get() is Activity &&
        (mContext?.get()  as Activity).isFinishing){
            return
    }

Kurtis is right. However, if you REALLY want to keep it simple, you can try this:

class MyActivity extends Activity {
    static MyActivity context;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        MyActivity.context = this;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        MyActivity.context = null;
    }
}

And then you just use MyActivity.context in your class (and check for null there). If you want the toast to not even show up when your app is in the background, use onPause/onResume instead.

Again, this is the quick and lazy approach. AsyncTask or AsyncTaskLoader is how you should be doing things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM