Merge branch '714-asynchronous-context-leaks' into 'master'

Fixed asynchronous Activity leaks in Fragments

If a Fragment has been detached its `getActivity()` method will return null, providing numerous crash possibilities within the app.

My approach to fixing this is to make Fragments use their own `runOnUiThreadUnlessDestroyed` method, which also checks if the Fragment has been detached before running the Runnable

Closes #714

See merge request !387
This commit is contained in:
akwizgran
2016-11-08 11:49:45 +00:00
12 changed files with 54 additions and 39 deletions

View File

@@ -47,7 +47,7 @@ import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
public class BlogFragment extends BaseFragment implements public class BlogFragment extends BaseFragment implements
OnBlogPostAddedListener { OnBlogPostAddedListener {
public final static String TAG = BlogFragment.class.getName(); private final static String TAG = BlogFragment.class.getName();
@Inject @Inject
BlogController blogController; BlogController blogController;
@@ -178,7 +178,7 @@ public class BlogFragment extends BaseFragment implements
public void onBlogPostAdded(BlogPostHeader header, final boolean local) { public void onBlogPostAdded(BlogPostHeader header, final boolean local) {
blogController.loadBlogPost(header, blogController.loadBlogPost(header,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
adapter.add(post); adapter.add(post);
@@ -201,10 +201,10 @@ public class BlogFragment extends BaseFragment implements
); );
} }
void loadBlogPosts(final boolean reload) { private void loadBlogPosts(final boolean reload) {
blogController.loadBlogPosts( blogController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.isEmpty()) { if (posts.isEmpty()) {
@@ -225,7 +225,7 @@ public class BlogFragment extends BaseFragment implements
private void loadBlog() { private void loadBlog() {
blogController.loadBlog( blogController.loadBlog(
new UiResultExceptionHandler<BlogItem, DbException>(listener) { new UiResultExceptionHandler<BlogItem, DbException>(this) {
@Override @Override
public void onResultUi(BlogItem blog) { public void onResultUi(BlogItem blog) {
setToolbarTitle(blog.getBlog().getAuthor()); setToolbarTitle(blog.getBlog().getAuthor());
@@ -299,7 +299,7 @@ public class BlogFragment extends BaseFragment implements
private void deleteBlog() { private void deleteBlog() {
blogController.deleteBlog( blogController.deleteBlog(
new UiResultExceptionHandler<Void, DbException>(listener) { new UiResultExceptionHandler<Void, DbException>(this) {
@Override @Override
public void onResultUi(Void result) { public void onResultUi(Void result) {
Toast.makeText(getActivity(), Toast.makeText(getActivity(),

View File

@@ -62,7 +62,7 @@ public class BlogPostFragment extends BasePostFragment {
super.onStart(); super.onStart();
blogController.loadBlogPost(postId, blogController.loadBlogPost(postId,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
onBlogPostLoaded(post); onBlogPostLoaded(post);

View File

@@ -45,7 +45,7 @@ public class BlogPostPagerFragment extends BasePostPagerFragment {
void loadBlogPosts(final MessageId select) { void loadBlogPosts(final MessageId select) {
blogController.loadBlogPosts( blogController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
onBlogPostsLoaded(select, posts); onBlogPostsLoaded(select, posts);
@@ -62,7 +62,7 @@ public class BlogPostPagerFragment extends BasePostPagerFragment {
void loadBlogPost(BlogPostHeader header) { void loadBlogPost(BlogPostHeader header) {
blogController.loadBlogPost(header, blogController.loadBlogPost(header,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
addPost(post); addPost(post);

View File

@@ -114,7 +114,7 @@ public class FeedFragment extends BaseFragment implements
private void loadPersonalBlog() { private void loadPersonalBlog() {
feedController.loadPersonalBlog( feedController.loadPersonalBlog(
new UiResultExceptionHandler<Blog, DbException>(listener) { new UiResultExceptionHandler<Blog, DbException>(this) {
@Override @Override
public void onResultUi(Blog b) { public void onResultUi(Blog b) {
personalBlog = b; personalBlog = b;
@@ -131,7 +131,7 @@ public class FeedFragment extends BaseFragment implements
final int revision = adapter.getRevision(); final int revision = adapter.getRevision();
feedController.loadBlogPosts( feedController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
@@ -194,7 +194,7 @@ public class FeedFragment extends BaseFragment implements
public void onBlogPostAdded(BlogPostHeader header, final boolean local) { public void onBlogPostAdded(BlogPostHeader header, final boolean local) {
feedController.loadBlogPost(header, feedController.loadBlogPost(header,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
adapter.incrementRevision(); adapter.incrementRevision();

View File

@@ -65,7 +65,7 @@ public class FeedPostFragment extends BasePostFragment {
super.onStart(); super.onStart();
feedController.loadBlogPost(blogId, postId, feedController.loadBlogPost(blogId, postId,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
onBlogPostLoaded(post); onBlogPostLoaded(post);

View File

@@ -44,7 +44,7 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
void loadBlogPosts(final MessageId select) { void loadBlogPosts(final MessageId select) {
feedController.loadBlogPosts( feedController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
onBlogPostsLoaded(select, posts); onBlogPostsLoaded(select, posts);
@@ -61,7 +61,7 @@ public class FeedPostPagerFragment extends BasePostPagerFragment {
void loadBlogPost(BlogPostHeader header) { void loadBlogPost(BlogPostHeader header) {
feedController.loadBlogPost(header, feedController.loadBlogPost(header,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
addPost(post); addPost(post);

View File

@@ -93,7 +93,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
// TODO: Load blog post when fragment is created. #631 // TODO: Load blog post when fragment is created. #631
feedController.loadBlogPost(blogId, postId, feedController.loadBlogPost(blogId, postId,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(BlogPostItem result) { public void onResultUi(BlogPostItem result) {
item = result; item = result;
@@ -130,7 +130,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
public void onSendClick(String text) { public void onSendClick(String text) {
String comment = getComment(); String comment = getComment();
feedController.repeatPost(item, comment, feedController.repeatPost(item, comment,
new UiResultExceptionHandler<Void, DbException>(listener) { new UiResultExceptionHandler<Void, DbException>(this) {
@Override @Override
public void onResultUi(Void result) { public void onResultUi(Void result) {
// do nothing, this fragment is gone already // do nothing, this fragment is gone already

View File

@@ -223,7 +223,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
private void displayContacts(final int revision, private void displayContacts(final int revision,
final List<ContactListItem> contacts) { final List<ContactListItem> contacts) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
@@ -289,7 +289,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
private void updateItem(final ContactId c, final BaseMessageHeader h) { private void updateItem(final ContactId c, final BaseMessageHeader h) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();
@@ -305,7 +305,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
private void removeItem(final ContactId c) { private void removeItem(final ContactId c) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();
@@ -317,7 +317,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
private void setConnected(final ContactId c, final boolean connected) { private void setConnected(final ContactId c, final boolean connected) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();

View File

@@ -178,7 +178,7 @@ public class ForumListFragment extends BaseEventFragment implements
private void displayForums(final int revision, private void displayForums(final int revision,
final Collection<ForumListItem> forums) { final Collection<ForumListItem> forums) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
@@ -214,7 +214,7 @@ public class ForumListFragment extends BaseEventFragment implements
} }
private void displayAvailableForums(final int availableCount) { private void displayAvailableForums(final int availableCount) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (availableCount == 0) { if (availableCount == 0) {
@@ -257,7 +257,7 @@ public class ForumListFragment extends BaseEventFragment implements
} }
private void updateItem(final GroupId g, final ForumPostHeader m) { private void updateItem(final GroupId g, final ForumPostHeader m) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();
@@ -272,7 +272,7 @@ public class ForumListFragment extends BaseEventFragment implements
} }
private void removeForum(final GroupId g) { private void removeForum(final GroupId g) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();

View File

@@ -1,7 +1,9 @@
package org.briarproject.android.fragment; package org.briarproject.android.fragment;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@@ -33,7 +35,6 @@ public abstract class BaseFragment extends Fragment
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@Override @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@@ -54,10 +55,11 @@ public abstract class BaseFragment extends Fragment
@UiThread @UiThread
protected void finish() { protected void finish() {
getActivity().supportFinishAfterTransition(); if (!isDetached())
getActivity().supportFinishAfterTransition();
} }
public interface BaseFragmentListener extends DestroyableContext { public interface BaseFragmentListener {
@Deprecated @Deprecated
void runOnDbThread(Runnable runnable); void runOnDbThread(Runnable runnable);
@@ -72,8 +74,21 @@ public abstract class BaseFragment extends Fragment
void onFragmentCreated(String tag); void onFragmentCreated(String tag);
} }
@CallSuper
@Override @Override
public void runOnUiThreadUnlessDestroyed(Runnable r) { public void runOnUiThreadUnlessDestroyed(final Runnable r) {
listener.runOnUiThreadUnlessDestroyed(r); final Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// Note that we don't have to check if the activity has
// been destroyed as the Fragment has not been detached yet
if (!isDetached() && !activity.isFinishing()) {
r.run();
}
}
});
}
} }
} }

View File

@@ -272,7 +272,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e; KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e;
keyAgreementAborted(event.didRemoteAbort()); keyAgreementAborted(event.didRemoteAbort());
} else if (e instanceof KeyAgreementFinishedEvent) { } else if (e instanceof KeyAgreementFinishedEvent) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
mainProgressContainer.setVisibility(VISIBLE); mainProgressContainer.setVisibility(VISIBLE);
@@ -313,7 +313,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} }
private void setQrCode(final Payload localPayload) { private void setQrCode(final Payload localPayload) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
generateBitmapQR(localPayload); generateBitmapQR(localPayload);
@@ -322,7 +322,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} }
private void keyAgreementFailed() { private void keyAgreementFailed() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
reset(); reset();
@@ -334,7 +334,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} }
private void keyAgreementWaiting() { private void keyAgreementWaiting() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
status.setText(R.string.waiting_for_contact_to_scan); status.setText(R.string.waiting_for_contact_to_scan);
@@ -343,7 +343,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} }
private void keyAgreementStarted() { private void keyAgreementStarted() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
mainProgressContainer.setVisibility(VISIBLE); mainProgressContainer.setVisibility(VISIBLE);
@@ -353,7 +353,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
} }
private void keyAgreementAborted(final boolean remoteAborted) { private void keyAgreementAborted(final boolean remoteAborted) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
reset(); reset();
@@ -370,7 +370,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
@Override @Override
public void handleResult(final Result result) { public void handleResult(final Result result) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Got result from decoder"); LOG.info("Got result from decoder");

View File

@@ -126,7 +126,7 @@ public class GroupListFragment extends BaseFragment implements
@Override @Override
public void onGroupRemoveClick(GroupItem item) { public void onGroupRemoveClick(GroupItem item) {
controller.removeGroup(item.getId(), controller.removeGroup(item.getId(),
new UiResultExceptionHandler<Void, DbException>(listener) { new UiResultExceptionHandler<Void, DbException>(this) {
@Override @Override
public void onResultUi(Void result) { public void onResultUi(Void result) {
// handled by GroupRemovedEvent and onGroupRemoved() // handled by GroupRemovedEvent and onGroupRemoved()
@@ -173,7 +173,7 @@ public class GroupListFragment extends BaseFragment implements
final int revision = adapter.getRevision(); final int revision = adapter.getRevision();
controller.loadGroups( controller.loadGroups(
new UiResultExceptionHandler<Collection<GroupItem>, DbException>( new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
listener) { this) {
@Override @Override
public void onResultUi(Collection<GroupItem> groups) { public void onResultUi(Collection<GroupItem> groups) {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {