jenkins-bot has submitted this change and it was merged.
Change subject: Fix memory leaks (for real!), part 1.
......................................................................
Fix memory leaks (for real!), part 1.
My ongoing search has finally yielded some tangible nuggets.
Exhibit A: Adding a TextWatcher to a TextView, but never removing it! (The
TextWatcher holds a reference to the fragment, thereby leaking it)
Also, now explicitly removing the onItemClick and onItemLongClick
listeners from the ListView, since this seems to lead to a leak in
API <20.
Change-Id: I497ebd37f3059b420622416a66ee5b87a15a2d0c
---
M app/src/main/java/org/wikipedia/history/HistoryFragment.java
M app/src/main/java/org/wikipedia/savedpages/SavedPagesFragment.java
2 files changed, 231 insertions(+), 215 deletions(-)
Approvals:
Niedzielski: Looks good to me, approved
jenkins-bot: Verified
diff --git a/app/src/main/java/org/wikipedia/history/HistoryFragment.java
b/app/src/main/java/org/wikipedia/history/HistoryFragment.java
index 52aea5c..223f6fc 100644
--- a/app/src/main/java/org/wikipedia/history/HistoryFragment.java
+++ b/app/src/main/java/org/wikipedia/history/HistoryFragment.java
@@ -55,6 +55,9 @@
private WikipediaApp app;
private ActionMode actionMode;
+ private HistorySearchTextWatcher textWatcher = new
HistorySearchTextWatcher();
+ private HistoryItemClickListener itemClickListener = new
HistoryItemClickListener();
+ private HistoryItemLongClickListener itemLongClickListener = new
HistoryItemLongClickListener();
private boolean firstRun = true;
@@ -63,20 +66,24 @@
super.onCreate(savedInstanceState);
setRetainInstance(true);
app = WikipediaApp.getInstance();
+ adapter = new HistoryEntryAdapter(getContext(), null, true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_history, container,
false);
rootView.setPadding(0, getContentTopOffsetPx(getActivity()), 0, 0);
-
historyEntryList = (ListView)
rootView.findViewById(R.id.history_entry_list);
historyEmptyContainer =
rootView.findViewById(R.id.history_empty_container);
historyEmptyTitle = (TextView)
rootView.findViewById(R.id.history_empty_title);
historyEmptyMessage = (TextView)
rootView.findViewById(R.id.history_empty_message);
entryFilter = (EditText)
rootView.findViewById(R.id.history_search_list);
-
app.adjustDrawableToTheme(((ImageView)
rootView.findViewById(R.id.history_empty_image)).getDrawable());
+
+ entryFilter.addTextChangedListener(textWatcher);
+ historyEntryList.setAdapter(adapter);
+ historyEntryList.setOnItemClickListener(itemClickListener);
+ historyEntryList.setOnItemLongClickListener(itemLongClickListener);
return rootView;
}
@@ -84,102 +91,7 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
- adapter = new HistoryEntryAdapter(getActivity(), null, true);
- historyEntryList.setAdapter(adapter);
historyEntryList.setEmptyView(historyEmptyContainer);
-
- entryFilter.addTextChangedListener(
- new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence,
int i, int i2, int i3) {
- // Do nothing
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int
i, int i2, int i3) {
- // Do nothing
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
-
getActivity().getSupportLoaderManager().restartLoader(HISTORY_FRAGMENT_LOADER_ID,
null, HistoryFragment.this);
- if (editable.length() == 0) {
-
historyEmptyTitle.setText(R.string.history_empty_title);
- historyEmptyMessage.setVisibility(View.VISIBLE);
- } else {
-
historyEmptyTitle.setText(getString(R.string.history_search_empty_message));
- historyEmptyMessage.setVisibility(View.GONE);
- }
- }
- });
-
- historyEntryList.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int
position, long id) {
- if (actionMode == null) {
- HistoryEntry oldEntry = (HistoryEntry) view.getTag();
- HistoryEntry newEntry = new
HistoryEntry(oldEntry.getTitle(), HistoryEntry.SOURCE_HISTORY);
- ((PageActivity)
getActivity()).loadPage(oldEntry.getTitle(), newEntry);
- }
- }
- });
-
- historyEntryList.setOnItemLongClickListener(new
AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
- if (actionMode != null) {
- return false;
- }
-
historyEntryList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
- actionMode =
((AppCompatActivity)getActivity()).startSupportActionMode(new
ActionMode.Callback() {
- private final String actionModeTag = "actionModeHistory";
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu
menu) {
-
mode.getMenuInflater().inflate(R.menu.menu_history_context, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu
menu) {
- mode.setTag(actionModeTag);
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode,
MenuItem item) {
- if (item.getItemId() ==
R.id.menu_delete_selected_history) {
- SparseBooleanArray checkedItems =
historyEntryList.getCheckedItemPositions();
- for (int i = 0; i < checkedItems.size(); i++) {
- if (checkedItems.valueAt(i)) {
-
app.getDatabaseClient(HistoryEntry.class).delete(
-
HistoryEntry.DATABASE_TABLE.fromCursor((Cursor)
adapter.getItem(checkedItems.keyAt(i))),
-
HistoryEntryDatabaseTable.SELECTION_KEYS);
- }
- }
- if (checkedItems.size() ==
historyEntryList.getAdapter().getCount()) {
- entryFilter.setVisibility(View.GONE);
- }
- mode.finish();
- return true;
- } else {
- throw new RuntimeException("Unknown context menu
item clicked");
- }
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
-
historyEntryList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
- actionMode = null;
- // Clear all selections
- historyEntryList.clearChoices();
- historyEntryList.requestLayout(); // Required to
immediately redraw unchecked states
- }
- });
- historyEntryList.setItemChecked(position, true);
- return true;
- }
- });
-
getActivity().getSupportLoaderManager().initLoader(HISTORY_FRAGMENT_LOADER_ID,
null, this);
getActivity().getSupportLoaderManager().restartLoader(HISTORY_FRAGMENT_LOADER_ID,
null, this);
}
@@ -187,6 +99,10 @@
@Override
public void onDestroyView() {
getActivity().getSupportLoaderManager().destroyLoader(HISTORY_FRAGMENT_LOADER_ID);
+ entryFilter.removeTextChangedListener(textWatcher);
+ historyEntryList.setOnItemClickListener(null);
+ historyEntryList.setOnItemLongClickListener(null);
+ historyEntryList.setAdapter(null);
super.onDestroyView();
}
@@ -331,4 +247,95 @@
return super.onOptionsItemSelected(item);
}
}
+
+ private class HistoryItemClickListener implements
AdapterView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int
position, long id) {
+ if (actionMode == null) {
+ HistoryEntry oldEntry = (HistoryEntry) view.getTag();
+ HistoryEntry newEntry = new HistoryEntry(oldEntry.getTitle(),
HistoryEntry.SOURCE_HISTORY);
+ ((PageActivity) getActivity()).loadPage(oldEntry.getTitle(),
newEntry);
+ }
+ }
+ }
+
+ private class HistoryItemLongClickListener implements
AdapterView.OnItemLongClickListener {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int
position, long id) {
+ if (actionMode != null) {
+ return false;
+ }
+ historyEntryList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
+ actionMode =
((AppCompatActivity)getActivity()).startSupportActionMode(new
ActionMode.Callback() {
+ private final String actionModeTag = "actionModeHistory";
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+
mode.getMenuInflater().inflate(R.menu.menu_history_context, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu)
{
+ mode.setTag(actionModeTag);
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem
item) {
+ if (item.getItemId() == R.id.menu_delete_selected_history)
{
+ SparseBooleanArray checkedItems =
historyEntryList.getCheckedItemPositions();
+ for (int i = 0; i < checkedItems.size(); i++) {
+ if (checkedItems.valueAt(i)) {
+
app.getDatabaseClient(HistoryEntry.class).delete(
+
HistoryEntry.DATABASE_TABLE.fromCursor((Cursor)
adapter.getItem(checkedItems.keyAt(i))),
+
HistoryEntryDatabaseTable.SELECTION_KEYS);
+ }
+ }
+ if (checkedItems.size() ==
historyEntryList.getAdapter().getCount()) {
+ entryFilter.setVisibility(View.GONE);
+ }
+ mode.finish();
+ return true;
+ } else {
+ throw new RuntimeException("Unknown context menu item
clicked");
+ }
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+
historyEntryList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+ actionMode = null;
+ // Clear all selections
+ historyEntryList.clearChoices();
+ historyEntryList.requestLayout(); // Required to
immediately redraw unchecked states
+ }
+ });
+ historyEntryList.setItemChecked(position, true);
+ return true;
+ }
+ }
+
+ private class HistorySearchTextWatcher implements TextWatcher {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int
i2, int i3) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2,
int i3) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+
getActivity().getSupportLoaderManager().restartLoader(HISTORY_FRAGMENT_LOADER_ID,
null, HistoryFragment.this);
+ if (editable.length() == 0) {
+ historyEmptyTitle.setText(R.string.history_empty_title);
+ historyEmptyMessage.setVisibility(View.VISIBLE);
+ } else {
+
historyEmptyTitle.setText(getString(R.string.history_search_empty_message,
editable.toString()));
+ historyEmptyMessage.setVisibility(View.GONE);
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/wikipedia/savedpages/SavedPagesFragment.java
b/app/src/main/java/org/wikipedia/savedpages/SavedPagesFragment.java
index 736ab2d..64f7973 100644
--- a/app/src/main/java/org/wikipedia/savedpages/SavedPagesFragment.java
+++ b/app/src/main/java/org/wikipedia/savedpages/SavedPagesFragment.java
@@ -61,6 +61,9 @@
private WikipediaApp app;
private ActionMode actionMode;
+ private SavedPageSearchTextWatcher textWatcher = new
SavedPageSearchTextWatcher();
+ private SavedPageItemClickListener itemClickListener = new
SavedPageItemClickListener();
+ private SavedPageItemLongClickListener itemLongClickListener = new
SavedPageItemLongClickListener();
private boolean firstRun = true;
@@ -69,6 +72,7 @@
super.onCreate(savedInstanceState);
setRetainInstance(true);
app = WikipediaApp.getInstance();
+ adapter = new SavedPagesAdapter(getActivity(), null, true);
}
@Override
@@ -82,6 +86,12 @@
savedPagesEmptyMessage = (TextView)
rootView.findViewById(R.id.saved_pages_empty_message);
entryFilter = (EditText)
rootView.findViewById(R.id.saved_pages_search_list);
savedPagesEmptyImage = (ImageView)
rootView.findViewById(R.id.saved_pages_empty_image);
+ app.adjustDrawableToTheme(savedPagesEmptyImage.getDrawable());
+
+ entryFilter.addTextChangedListener(textWatcher);
+ savedPagesList.setAdapter(adapter);
+ savedPagesList.setOnItemClickListener(itemClickListener);
+ savedPagesList.setOnItemLongClickListener(itemLongClickListener);
return rootView;
}
@@ -89,125 +99,7 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
- adapter = new SavedPagesAdapter(getActivity(), null, true);
- savedPagesList.setAdapter(adapter);
savedPagesList.setEmptyView(savedPagesEmptyContainer);
-
- savedPagesList.setOnItemLongClickListener(new
AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
- if (actionMode != null) {
- return false;
- }
- savedPagesList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
- actionMode =
((AppCompatActivity)getActivity()).startSupportActionMode(new
ActionMode.Callback() {
- private final String actionModeTag =
"actionModeSavedPages";
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu
menu) {
-
mode.getMenuInflater().inflate(R.menu.menu_saved_pages_context, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu
menu) {
- mode.setTag(actionModeTag);
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode,
MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_refresh_selected_saved_pages:
- refreshSelected();
- actionMode.finish();
- return true;
- case R.id.menu_delete_selected_saved_pages:
- deleteSelected();
- actionMode.finish();
- return true;
- default:
- throw new RuntimeException("Unknown context
menu item clicked");
- }
- }
-
- private void deleteSelected() {
- SparseBooleanArray checkedItems =
savedPagesList.getCheckedItemPositions();
- for (int i = 0; i < checkedItems.size(); i++) {
- if (checkedItems.valueAt(i)) {
- final SavedPage page =
SavedPage.DATABASE_TABLE.fromCursor((Cursor)
adapter.getItem(checkedItems.keyAt(i)));
- new DeleteSavedPageTask(getActivity(), page) {
- @Override
- public void onFinish(Boolean result) {
- if (!isAdded()) {
- return;
- }
- FeedbackUtil.showMessage(getActivity(),
-
R.string.snackbar_saved_page_deleted);
- }
- }.execute();
- }
- }
- if (checkedItems.size() ==
savedPagesList.getAdapter().getCount()) {
- entryFilter.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
-
savedPagesList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
- actionMode = null;
- // Clear all selections
- savedPagesList.clearChoices();
- savedPagesList.requestLayout(); // Required to
immediately redraw unchecked states
-
- }
- });
- savedPagesList.setItemChecked(position, true);
- return true;
- }
- });
-
- entryFilter.addTextChangedListener(
- new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence,
int i, int i2, int i3) {
- // Do nothing
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int
i, int i2, int i3) {
- // Do nothing
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
-
getActivity().getSupportLoaderManager().restartLoader(SAVED_PAGES_FRAGMENT_LOADER_ID,
null, SavedPagesFragment.this);
- if (editable.length() == 0) {
-
savedPagesEmptyTitle.setText(R.string.saved_pages_empty_title);
- savedPagesEmptyImage.setVisibility(View.VISIBLE);
- savedPagesEmptyMessage.setVisibility(View.VISIBLE);
- } else {
-
savedPagesEmptyTitle.setText(getString(R.string.saved_pages_search_empty_message));
- savedPagesEmptyImage.setVisibility(View.GONE);
- savedPagesEmptyMessage.setVisibility(View.GONE);
- }
- }
- });
-
- savedPagesList.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int
position, long id) {
- // We shouldn't do anything if the user is multi-selecting
things
- if (actionMode == null) {
- SavedPage savedPage = (SavedPage) view.getTag();
- HistoryEntry newEntry = new
HistoryEntry(savedPage.getTitle(), HistoryEntry.SOURCE_SAVED_PAGE);
-
((PageActivity)getActivity()).loadPage(savedPage.getTitle(), newEntry);
- }
- }
- });
-
- app.adjustDrawableToTheme(savedPagesEmptyImage.getDrawable());
-
getActivity().getSupportLoaderManager().initLoader(SAVED_PAGES_FRAGMENT_LOADER_ID,
null, this);
getActivity().getSupportLoaderManager().restartLoader(SAVED_PAGES_FRAGMENT_LOADER_ID,
null, this);
}
@@ -215,6 +107,10 @@
@Override
public void onDestroyView() {
getActivity().getSupportLoaderManager().destroyLoader(SAVED_PAGES_FRAGMENT_LOADER_ID);
+ entryFilter.removeTextChangedListener(textWatcher);
+ savedPagesList.setAdapter(null);
+ savedPagesList.setOnItemClickListener(null);
+ savedPagesList.setOnItemLongClickListener(null);
super.onDestroyView();
}
@@ -393,4 +289,117 @@
}
super.onStop();
}
+
+ private class SavedPageItemClickListener implements
AdapterView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int
position, long id) {
+ // We shouldn't do anything if the user is multi-selecting things
+ if (actionMode == null) {
+ SavedPage savedPage = (SavedPage) view.getTag();
+ HistoryEntry newEntry = new HistoryEntry(savedPage.getTitle(),
HistoryEntry.SOURCE_SAVED_PAGE);
+ ((PageActivity)getActivity()).loadPage(savedPage.getTitle(),
newEntry);
+ }
+ }
+ }
+
+ private class SavedPageItemLongClickListener implements
AdapterView.OnItemLongClickListener {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int
position, long id) {
+ if (actionMode != null) {
+ return false;
+ }
+ savedPagesList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
+ actionMode = ((AppCompatActivity)
getActivity()).startSupportActionMode(new ActionMode.Callback() {
+ private final String actionModeTag = "actionModeSavedPages";
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+
mode.getMenuInflater().inflate(R.menu.menu_saved_pages_context, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu)
{
+ mode.setTag(actionModeTag);
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem
item) {
+ switch (item.getItemId()) {
+ case R.id.menu_refresh_selected_saved_pages:
+ refreshSelected();
+ actionMode.finish();
+ return true;
+ case R.id.menu_delete_selected_saved_pages:
+ deleteSelected();
+ actionMode.finish();
+ return true;
+ default:
+ throw new RuntimeException("Unknown context menu
item clicked");
+ }
+ }
+
+ private void deleteSelected() {
+ SparseBooleanArray checkedItems =
savedPagesList.getCheckedItemPositions();
+ for (int i = 0; i < checkedItems.size(); i++) {
+ if (checkedItems.valueAt(i)) {
+ final SavedPage page =
SavedPage.DATABASE_TABLE.fromCursor((Cursor)
adapter.getItem(checkedItems.keyAt(i)));
+ new DeleteSavedPageTask(getActivity(), page) {
+ @Override
+ public void onFinish(Boolean result) {
+ if (!isAdded()) {
+ return;
+ }
+ FeedbackUtil.showMessage(getActivity(),
+
R.string.snackbar_saved_page_deleted);
+ }
+ }.execute();
+ }
+ }
+ if (checkedItems.size() ==
savedPagesList.getAdapter().getCount()) {
+ entryFilter.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+
savedPagesList.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+ actionMode = null;
+ // Clear all selections
+ savedPagesList.clearChoices();
+ savedPagesList.requestLayout(); // Required to immediately
redraw unchecked states
+
+ }
+ });
+ savedPagesList.setItemChecked(position, true);
+ return true;
+ }
+ }
+
+ private class SavedPageSearchTextWatcher implements TextWatcher {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int
i2, int i3) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i2,
int i3) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+
getActivity().getSupportLoaderManager().restartLoader(SAVED_PAGES_FRAGMENT_LOADER_ID,
null, SavedPagesFragment.this);
+ if (editable.length() == 0) {
+ savedPagesEmptyTitle.setText(R.string.saved_pages_empty_title);
+ savedPagesEmptyImage.setVisibility(View.VISIBLE);
+ savedPagesEmptyMessage.setVisibility(View.VISIBLE);
+ } else {
+
savedPagesEmptyTitle.setText(getString(R.string.saved_pages_search_empty_message,
editable.toString()));
+ savedPagesEmptyImage.setVisibility(View.GONE);
+ savedPagesEmptyMessage.setVisibility(View.GONE);
+ }
+ }
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/271554
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I497ebd37f3059b420622416a66ee5b87a15a2d0c
Gerrit-PatchSet: 4
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits