Let’s say you’ve got some 20 bitmaps being decoded on a worker thread. Well, what happens if your activity is destroyed before that work completes?
相比于本期的大叔,笔者更喜欢之前的光头哥
Video Link


No One Writes to the Colonel

五十六年了,上校唯一做过的事情就是等待。

我们知道,Worker Thread是在UI Thread之外用来处理耗时任务的线程,存在这样一种情况,当任务由Worker Thread执行完毕时,引发这项任务的Activity已经走完了onDestroy,这会产生什么样的后果呢?

第一,Activity虽然结束了它的生命周期,可这个实例并没有从内存中释放——因为Worker Thread还拥有着Activity的reference,这会导致内存泄漏,如图1;第二,已经onDestroy过的Activity早已从屏幕上消失,无法响应任务执行后的返回,我们必须重新创建Activity2来处理任务返回后界面变化,如图2。

图1

图2


You Should Use Loaders

针对上面的场景,建议使用Loader。设计合理的Loader可以避免内存泄漏、正确处理UI事件、并且不需要重复执行。

LoaderManager是用来管理Loader的接口,借助前面链接中例子不难理解Loader运行的机制:获取LoaderManager实例-初始化Loader-实现LoaderCallbacks接口处理回调。

LoaderManager可以缓存任务执行结果,这样当Activity销毁并重建时,就不需要重复执行前面已经执行过的任务了。

如果一个Activity被终止后不会重建,我们就应该在LoaderManager的onLoaderReset中处理这种情形,释放引用。


The Downside of Loaders

集成较为复杂,代码量大。

参考下面Reference给出的例子,实现了Fragment的ListView从联系人ContentProvider中通过CursorLoader获取数据的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, OnCloseListener,
LoaderManager.LoaderCallbacks<Cursor> {

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;

// The SearchView for doing filtering.
SearchView mSearchView;

// If non-null, this is the current filter the user has provided.
String mCurFilter;

@Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");

// We have a menu item to show in action bar.
setHasOptionsMenu(true);

// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);

// Start out with a progress indicator.
setListShown(false);

// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}

public static class MySearchView extends SearchView {
public MySearchView(Context context) {
super(context);
}

// The normal SearchView doesn't clear its search text when
// collapsed, so we will do this for it.
@Override
public void onActionViewCollapsed() {
setQuery("", false);
super.onActionViewCollapsed();
}
}

@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
mSearchView = new MySearchView(getActivity());
mSearchView.setOnQueryTextListener(this);
mSearchView.setOnCloseListener(this);
mSearchView.setIconifiedByDefault(true);
item.setActionView(mSearchView);
}

public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
// Don't do anything if the filter hasn't actually changed.
// Prevents restarting the loader when restoring state.
if (mCurFilter == null && newFilter == null) {
return true;
}
if (mCurFilter != null && mCurFilter.equals(newFilter)) {
return true;
}
mCurFilter = newFilter;
getLoaderManager().restartLoader(0, null, this);
return true;
}

@Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}

@Override
public boolean onClose() {
if (!TextUtils.isEmpty(mSearchView.getQuery())) {
mSearchView.setQuery(null, true);
}
return true;
}

@Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}

// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}

// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);

// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}

public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}

===Ending===