5 Commits

Autor SHA1 Mensagem Data
Anthony Restaino dffd572afc Fix memory leaks caused by the android framework 2016-06-01 20:05:55 -04:00
Anthony Restaino b7f3defd19 Fix bug where AutoCompleteTextView selected text but didn't delete it when typing
Also added in window focus change callback so that we can animate UI in
correctly. Also other small changes
2016-05-25 21:35:38 -04:00
Anthony Restaino e11a718d3b Updated to gradle wrapper 2.10, updated to android gradle plugin 2.1 2016-05-19 22:54:15 -04:00
Anthony Restaino a47cede6c5 Updating gradle dependencies 2016-05-19 22:52:52 -04:00
Anthony Restaino b1a8b7a0d5 Add debug settings so that you can toggle LeakCanary (and other settings in the future) in debug 2016-05-09 21:52:18 -04:00
18 arquivos alterados com 353 adições e 51 exclusões
+8 -8
Ver Arquivo
@@ -70,14 +70,14 @@ dexcount {
dependencies {
// support libraries
compile 'com.android.support:palette-v7:23.3.0'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.android.support:recyclerview-v7:23.3.0'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:palette-v7:23.4.0'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.android.support:recyclerview-v7:23.4.0'
compile 'com.android.support:support-v4:23.4.0'
// html parsing fo reading mode
compile 'org.jsoup:jsoup:1.9.1'
// html parsing for reading mode
compile 'org.jsoup:jsoup:1.9.2'
// event bus
compile 'com.squareup:otto:1.3.8'
@@ -85,6 +85,7 @@ dependencies {
// dependency injection
compile 'com.google.dagger:dagger:2.0.2'
apt 'com.google.dagger:dagger-compiler:2.0.2'
provided 'javax.annotation:jsr250-api:1.0'
// view binding
compile 'com.jakewharton:butterknife:7.0.1'
@@ -103,5 +104,4 @@ dependencies {
// memory leak analysis
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
provided 'javax.annotation:jsr250-api:1.0'
}
@@ -43,6 +43,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar;
import android.text.Selection;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
@@ -125,6 +126,7 @@ import acr.browser.lightning.utils.Utils;
import acr.browser.lightning.utils.WebUtils;
import acr.browser.lightning.view.AnimatedProgressBar;
import acr.browser.lightning.view.LightningView;
import acr.browser.lightning.view.SearchView;
import butterknife.Bind;
import butterknife.ButterKnife;
@@ -152,7 +154,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// Toolbar Views
private View mSearchBackground;
private Toolbar mToolbar;
private AutoCompleteTextView mSearch;
private SearchView mSearch;
private ImageView mArrowImage;
// Current tab view being displayed
@@ -372,7 +374,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
arrowButton.setOnClickListener(this);
// create the search EditText in the ToolBar
mSearch = (AutoCompleteTextView) customView.findViewById(R.id.search);
mSearch = (SearchView) customView.findViewById(R.id.search);
mSearchBackground = customView.findViewById(R.id.search_container);
// initialize search background color
@@ -397,6 +399,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mSearch.setOnFocusChangeListener(search);
mSearch.setOnEditorActionListener(search);
mSearch.setOnTouchListener(search);
mSearch.setOnPreFocusListener(search);
initializeSearchSuggestions(mSearch);
@@ -437,12 +440,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
System.exit(1);
}
private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, OnFocusChangeListener, OnTouchListener {
private class SearchListenerClass implements OnKeyListener, OnEditorActionListener,
OnFocusChangeListener, OnTouchListener, SearchView.PreFocusListener {
@Override
public boolean onKey(View arg0, int arg1, KeyEvent arg2) {
public boolean onKey(View searchView, int keyCode, KeyEvent keyEvent) {
switch (arg1) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0);
@@ -480,20 +484,15 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
}
@Override
public void onFocusChange(View v, final boolean hasFocus) {
public void onFocusChange(final View v, final boolean hasFocus) {
final LightningView currentView = mTabsManager.getCurrentTab();
if (!hasFocus && currentView != null) {
setIsLoading(currentView.getProgress() < 100);
updateUrl(currentView.getUrl(), true);
} else if (hasFocus && currentView != null) {
String url = currentView.getUrl();
if (UrlUtils.isSpecialUrl(url)) {
mSearch.setText("");
} else {
mSearch.setText(url);
}
// Hack to make sure the text gets selected
((AutoCompleteTextView) v).selectAll();
((SearchView) v).selectAll();
mIcon = mClearIcon;
mSearch.setCompoundDrawables(null, null, mClearIcon, null);
}
@@ -522,6 +521,20 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
}
return false;
}
@Override
public void onPreFocus() {
final LightningView currentView = mTabsManager.getCurrentTab();
if (currentView == null) {
return;
}
String url = currentView.getUrl();
if (UrlUtils.isSpecialUrl(url)) {
mSearch.setText("");
} else {
mSearch.setText(url);
}
}
}
private class DrawerLocker implements DrawerListener {
@@ -612,8 +625,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// TODO layout transition causing memory leak
// mBrowserFrame.setLayoutTransition(new LayoutTransition());
mToolbarLayout.setTranslationY(0);
mBrowserFrame.setTranslationY(0);
setFullscreen(mPreferences.getHideStatusBarEnabled(), false);
initializeTabHeight();
@@ -675,6 +686,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mProxyUtils.updateProxySettings(this);
}
@Override
public void onWindowVisibleToUserAfterResume() {
super.onWindowVisibleToUserAfterResume();
mToolbarLayout.setTranslationY(0);
mBrowserFrame.setTranslationY(0);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
@@ -925,7 +943,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
// Use a delayed handler to make the transition smooth
// otherwise it will get caught up with the showTab code
// and cause a janky motion
mDrawerHandler.postDelayed(new Runnable() {
mDrawerHandler.postDelayed(new Runnable() {
@Override
public void run() {
mDrawerLayout.closeDrawers();
@@ -1230,9 +1248,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
mSuggestionsAdapter.refreshPreferences();
mSuggestionsAdapter.refreshBookmarks();
}
mTabsManager.resumeAll();
mTabsManager.resumeAll(this);
initializePreferences();
mTabsManager.resume(this);
supportInvalidateOptionsMenu();
@@ -1888,6 +1905,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
@Override
public void showActionBar() {
if (mFullScreen) {
Log.d(TAG, "showActionBar");
if (mToolbarLayout == null)
return;
@@ -15,13 +15,15 @@ import android.widget.LinearLayout;
import com.anthonycr.grant.PermissionsManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
public class SettingsActivity extends ThemableSettingsActivity {
private static final List<String> mFragments = new ArrayList<>(6);
private static final List<String> mFragments = new ArrayList<>(7);
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -45,12 +47,23 @@ public class SettingsActivity extends ThemableSettingsActivity {
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preferences_headers, target);
mFragments.clear();
for (Header header : target) {
Iterator<Header> headerIterator = target.iterator();
while (headerIterator.hasNext()) {
Header header = headerIterator.next();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// Workaround for bug in the AppCompat support library
header.iconRes = R.drawable.empty;
}
mFragments.add(header.fragment);
if (header.titleRes == R.string.debug_title) {
if (BrowserApp.isRelease()) {
headerIterator.remove();
} else {
mFragments.add(header.fragment);
}
} else {
mFragments.add(header.fragment);
}
}
}
@@ -196,8 +196,11 @@ public class TabsManager {
* WebView when the app is open currently due to a
* bug in the WebView, where calling onResume doesn't
* consistently resume it.
*
* @param context the context needed to initialize
* the LightningView preferences.
*/
public void resumeAll() {
public void resumeAll(@NonNull Context context) {
LightningView current = getCurrentTab();
if (current != null) {
current.resumeTimers();
@@ -205,6 +208,7 @@ public class TabsManager {
for (LightningView tab : mTabList) {
if (tab != null) {
tab.onResume();
tab.initializePreferences(context);
}
}
}
@@ -274,19 +278,6 @@ public class TabsManager {
mCurrentTab = null;
}
/**
* Reinitializes the preferences for
* all the tabs in the list.
*
* @param context the context needed
* to initialize the preferences.
*/
public synchronized void resume(@NonNull final Context context) {
for (LightningView tab : mTabList) {
tab.initializePreferences(context);
}
}
/**
* Forwards network connection status to the WebViews.
*
@@ -3,8 +3,12 @@ package acr.browser.lightning.activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayDeque;
import java.util.Queue;
import javax.inject.Inject;
import acr.browser.lightning.R;
@@ -17,6 +21,7 @@ public abstract class ThemableBrowserActivity extends AppCompatActivity {
private int mTheme;
private boolean mShowTabsInDrawer;
private boolean mShouldRunOnResumeActions = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -33,9 +38,29 @@ public abstract class ThemableBrowserActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && mShouldRunOnResumeActions) {
mShouldRunOnResumeActions = false;
onWindowVisibleToUserAfterResume();
}
}
/**
* Called after the activity is resumed
* and the UI becomes visible to the user.
* Called by onWindowFocusChanged only if
* onResume has been called.
*/
public void onWindowVisibleToUserAfterResume() {
}
@Override
protected void onResume() {
super.onResume();
mShouldRunOnResumeActions = true;
int theme = mPreferences.getUseTheme();
boolean drawerTabs = mPreferences.getShowTabsInDrawer(!isTablet());
if (theme != mTheme || mShowTabsInDrawer != drawerTabs) {
@@ -13,6 +13,7 @@ import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.download.LightningDownloadListener;
import acr.browser.lightning.fragment.BookmarkSettingsFragment;
import acr.browser.lightning.fragment.BookmarksFragment;
import acr.browser.lightning.fragment.DebugSettingsFragment;
import acr.browser.lightning.fragment.LightningPreferenceFragment;
import acr.browser.lightning.fragment.PrivacySettingsFragment;
import acr.browser.lightning.fragment.TabsFragment;
@@ -67,4 +68,6 @@ public interface AppComponent {
void inject(TabsManager manager);
void inject(DebugSettingsFragment fragment);
}
@@ -1,9 +1,11 @@
package acr.browser.lightning.app;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
import android.webkit.WebView;
import com.squareup.leakcanary.LeakCanary;
@@ -15,24 +17,40 @@ import java.util.concurrent.Executors;
import javax.inject.Inject;
import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.MemoryLeakUtils;
public class BrowserApp extends Application {
private static final String TAG = BrowserApp.class.getSimpleName();
private static AppComponent mAppComponent;
private static final Executor mIOThread = Executors.newSingleThreadExecutor();
private static final Executor mTaskThread = Executors.newCachedThreadPool();
@Inject Bus mBus;
@Inject PreferenceManager mPreferenceManager;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
mAppComponent.inject(this);
LeakCanary.install(this);
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (mPreferenceManager.getUseLeakCanary() && !isRelease()) {
LeakCanary.install(this);
}
if (!isRelease() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
registerActivityLifecycleCallbacks(new MemoryLeakUtils.LifecycleAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
Log.d(TAG, "Cleaning up after the Android framework");
MemoryLeakUtils.clearNextServedView(BrowserApp.this);
}
});
}
@NonNull
@@ -58,4 +76,13 @@ public class BrowserApp extends Application {
return get(context).mBus;
}
/**
* Determines whether this is a release build.
*
* @return true if this is a release build, false otherwise.
*/
public static boolean isRelease() {
return !BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.toLowerCase().equals("release");
}
}
@@ -0,0 +1,57 @@
package acr.browser.lightning.fragment;
import android.app.Activity;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
import android.support.annotation.NonNull;
import javax.inject.Inject;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.Utils;
public class DebugSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String LEAK_CANARY = "leak_canary_enabled";
@Inject PreferenceManager mPreferenceManager;
private SwitchPreference mSwitchLeakCanary;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this);
addPreferencesFromResource(R.xml.preference_debug);
mSwitchLeakCanary = (SwitchPreference) findPreference(LEAK_CANARY);
mSwitchLeakCanary.setChecked(mPreferenceManager.getUseLeakCanary());
mSwitchLeakCanary.setOnPreferenceChangeListener(this);
}
@Override
public boolean onPreferenceClick(@NonNull Preference preference) {
return false;
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, @NonNull Object newValue) {
switch (preference.getKey()) {
case LEAK_CANARY:
boolean value = Boolean.TRUE.equals(newValue);
mPreferenceManager.setUseLeakCanary(value);
Activity activity = getActivity();
if (activity != null) {
Utils.showSnackbar(activity, R.string.app_restart);
}
mSwitchLeakCanary.setChecked(value);
return true;
}
return false;
}
}
@@ -61,6 +61,8 @@ public class PreferenceManager {
public static final String USE_PROXY_PORT = "useProxyPort";
public static final String INITIAL_CHECK_FOR_TOR = "checkForTor";
public static final String INITIAL_CHECK_FOR_I2P = "checkForI2P";
public static final String LEAK_CANARY = "leakCanary";
}
@NonNull private final SharedPreferences mPrefs;
@@ -423,6 +425,14 @@ public class PreferenceManager {
putInt(Name.THEME, theme);
}
public void setUseLeakCanary(boolean useLeakCanary) {
putBoolean(Name.LEAK_CANARY, useLeakCanary);
}
public boolean getUseLeakCanary() {
return mPrefs.getBoolean(Name.LEAK_CANARY, false);
}
/**
* Valid choices:
* <ul>
@@ -0,0 +1,80 @@
package acr.browser.lightning.utils;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Method;
public class MemoryLeakUtils {
private static final String TAG = MemoryLeakUtils.class.getSimpleName();
private static Method sFinishInputLocked = null;
/**
* Clears the mNextServedView and mServedView in
* InputMethodManager and keeps them from leaking.
*
* @param application the application needed to get
* the InputMethodManager that is
* leaking the views.
*/
public static void clearNextServedView(@NonNull Application application) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// This shouldn't be a problem on N
return;
}
InputMethodManager imm = (InputMethodManager) application.getSystemService(Context.INPUT_METHOD_SERVICE);
if (sFinishInputLocked == null) {
try {
sFinishInputLocked = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
} catch (NoSuchMethodException e) {
Log.d(TAG, "Unable to find method in clearNextServedView", e);
}
}
if (sFinishInputLocked != null) {
sFinishInputLocked.setAccessible(true);
try {
sFinishInputLocked.invoke(imm);
} catch (Exception e) {
Log.d(TAG, "Unable to invoke method in clearNextServedView", e);
}
}
}
public static abstract class LifecycleAdapter implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
@Override
public void onActivityStarted(Activity activity) {}
@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onActivityStopped(Activity activity) {}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public void onActivityDestroyed(Activity activity) {}
}
}
@@ -233,7 +233,7 @@ public class LightningView {
* to get the default UserAgent for the WebView.
*/
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
public synchronized void initializePreferences(Context context) {
public synchronized void initializePreferences(@NonNull Context context) {
if (mWebView == null) {
return;
}
@@ -0,0 +1,62 @@
package acr.browser.lightning.view;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.AutoCompleteTextView;
public class SearchView extends AutoCompleteTextView {
public interface PreFocusListener {
void onPreFocus();
}
@Nullable private PreFocusListener mListener;
private boolean mIsBeingClicked;
private long mTimePressed;
public SearchView(Context context) {
super(context);
}
public SearchView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setOnPreFocusListener(@Nullable PreFocusListener listener) {
mListener = listener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mTimePressed = System.currentTimeMillis();
mIsBeingClicked = true;
break;
case MotionEvent.ACTION_CANCEL:
mIsBeingClicked = false;
break;
case MotionEvent.ACTION_UP:
if (mIsBeingClicked && !isLongPress()) {
if (mListener != null) {
mListener.onPreFocus();
}
}
break;
}
return super.onTouchEvent(event);
}
private boolean isLongPress() {
return (System.currentTimeMillis() - mTimePressed) >= ViewConfiguration.getLongPressTimeout();
}
}
+1 -2
Ver Arquivo
@@ -16,7 +16,7 @@
android:focusableInTouchMode="true"
android:gravity="center">
<AutoCompleteTextView
<acr.browser.lightning.view.SearchView
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -29,7 +29,6 @@
android:paddingLeft="8dp"
android:paddingRight="5dp"
android:paddingTop="1dp"
android:selectAllOnFocus="true"
android:singleLine="true"
android:textColor="@color/gray_dark"
android:textColorHint="@color/hint_text"
+5
Ver Arquivo
@@ -237,4 +237,9 @@
<string name="faq">FAQ</string>
<string name="faq_description">Frequently Asked Questions</string>
<!-- debug strings -->
<string name="debug_title">Debug Settings</string>
<string name="debug_leak_canary">LeakCanary</string>
<string name="app_restart">Please restart the app for the change to take effect.</string>
</resources>
+9
Ver Arquivo
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/debug_title">
<SwitchPreference
android:defaultValue="false"
android:key="leak_canary_enabled"
android:title="@string/debug_leak_canary"/>
</PreferenceCategory>
</PreferenceScreen>
@@ -30,4 +30,7 @@
android:value="SELF"/>
</intent>
</header>
<header
android:fragment="acr.browser.lightning.fragment.DebugSettingsFragment"
android:title="@string/debug_title"/>
</preference-headers>
+1 -1
Ver Arquivo
@@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.7'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.2.1'
}
+2 -2
Ver Arquivo
@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Thu May 19 22:48:07 EDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip