fix: app memory leak; feat: app foreground service
This commit is contained in:
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
@@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "org.autojs.autojs"
|
applicationId "org.autojs.autojs"
|
||||||
minSdkVersion 17
|
minSdkVersion 17
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 420
|
versionCode 421
|
||||||
versionName "4.0.3 Alpha"
|
versionName "4.0.3 Alpha2"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
ndk {
|
ndk {
|
||||||
@@ -24,7 +24,7 @@ android {
|
|||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
minifyEnabled false
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,10 +83,10 @@ dependencies {
|
|||||||
})
|
})
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'
|
||||||
// Android supports
|
// Android supports
|
||||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
compile 'com.android.support:appcompat-v7:27.1.1'
|
||||||
compile 'com.android.support:cardview-v7:25.4.0'
|
compile 'com.android.support:cardview-v7:27.1.1'
|
||||||
compile 'com.android.support:design:25.4.0'
|
compile 'com.android.support:design:27.1.1'
|
||||||
compile 'com.android.support:multidex:1.0.2'
|
compile 'com.android.support:multidex:1.0.3'
|
||||||
// Personal libraries
|
// Personal libraries
|
||||||
compile 'com.github.hyb1996:MutableTheme:0.2.2'
|
compile 'com.github.hyb1996:MutableTheme:0.2.2'
|
||||||
// Material Dialogs
|
// Material Dialogs
|
||||||
@@ -146,6 +146,10 @@ dependencies {
|
|||||||
compile('com.afollestad.material-dialogs:commons:0.9.2.3', {
|
compile('com.afollestad.material-dialogs:commons:0.9.2.3', {
|
||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
})
|
})
|
||||||
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
|
||||||
|
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
|
||||||
|
// Optional, if you use support library fragments:
|
||||||
|
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
|
||||||
compile project(':automator')
|
compile project(':automator')
|
||||||
compile project(':common')
|
compile project(':common')
|
||||||
compile project(':autojs')
|
compile project(':autojs')
|
||||||
|
|||||||
@@ -116,6 +116,7 @@
|
|||||||
android:theme="@style/IssueReporterTheme"/>
|
android:theme="@style/IssueReporterTheme"/>
|
||||||
|
|
||||||
|
|
||||||
|
<service android:name=".external.foreground.ForegroundService"/>
|
||||||
<service android:name=".external.ScriptExecutionIntentService"/>
|
<service android:name=".external.ScriptExecutionIntentService"/>
|
||||||
|
|
||||||
<activity android:name=".external.tasker.TaskPrefEditActivity_"/>
|
<activity android:name=".external.tasker.TaskPrefEditActivity_"/>
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.autojs.autojs;
|
package org.autojs.autojs;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.multidex.MultiDex;
|
||||||
import android.support.multidex.MultiDexApplication;
|
import android.support.multidex.MultiDexApplication;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -14,6 +17,7 @@ import com.raizlabs.android.dbflow.config.DatabaseConfig;
|
|||||||
import com.raizlabs.android.dbflow.config.FlowConfig;
|
import com.raizlabs.android.dbflow.config.FlowConfig;
|
||||||
import com.raizlabs.android.dbflow.config.FlowManager;
|
import com.raizlabs.android.dbflow.config.FlowManager;
|
||||||
import com.raizlabs.android.dbflow.runtime.DirectModelNotifier;
|
import com.raizlabs.android.dbflow.runtime.DirectModelNotifier;
|
||||||
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
import com.stardust.app.GlobalAppContext;
|
import com.stardust.app.GlobalAppContext;
|
||||||
import com.stardust.autojs.core.ui.inflater.ImageLoader;
|
import com.stardust.autojs.core.ui.inflater.ImageLoader;
|
||||||
import com.stardust.autojs.core.ui.inflater.util.Drawables;
|
import com.stardust.autojs.core.ui.inflater.util.Drawables;
|
||||||
@@ -67,6 +71,11 @@ public class App extends MultiDexApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
}
|
||||||
|
|
||||||
private void setUpDebugEnvironment() {
|
private void setUpDebugEnvironment() {
|
||||||
CrashHandler crashHandler = new CrashHandler(ErrorReportActivity.class);
|
CrashHandler crashHandler = new CrashHandler(ErrorReportActivity.class);
|
||||||
|
|
||||||
@@ -77,6 +86,12 @@ public class App extends MultiDexApplication {
|
|||||||
|
|
||||||
crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
|
crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
|
||||||
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
|
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
|
||||||
|
if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||||
|
// This process is dedicated to LeakCanary for heap analysis.
|
||||||
|
// You should not init your app in this process.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LeakCanary.install(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
|||||||
@@ -201,4 +201,8 @@ public class Pref {
|
|||||||
getString(R.string.default_value_script_dir_path));
|
getString(R.string.default_value_script_dir_path));
|
||||||
return new File(Environment.getExternalStorageDirectory(), dir).getPath();
|
return new File(Environment.getExternalStorageDirectory(), dir).getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isForegroundServiceEnabled() {
|
||||||
|
return def().getBoolean(getString(R.string.key_foreground_servie), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
app/src/main/java/org/autojs/autojs/external/foreground/ForegroundService.java
vendored
Normal file
58
app/src/main/java/org/autojs/autojs/external/foreground/ForegroundService.java
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package org.autojs.autojs.external.foreground;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.autojs.autojs.R;
|
||||||
|
|
||||||
|
public class ForegroundService extends Service {
|
||||||
|
private static final int NOTIFICATION_ID = 117;
|
||||||
|
private static final String CHANEL_ID = "foreground";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
startForeground();
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startForeground(){
|
||||||
|
startForeground(NOTIFICATION_ID, buildNotification());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification buildNotification() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel();
|
||||||
|
}
|
||||||
|
return new NotificationCompat.Builder(this, CHANEL_ID)
|
||||||
|
.setContentTitle(getString(R.string.foreground_notification_title))
|
||||||
|
.setContentText(getString(R.string.foreground_notification_text))
|
||||||
|
.setSmallIcon(R.drawable.autojs_material)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
|
private void createNotificationChannel(){
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
assert manager != null;
|
||||||
|
CharSequence name = getString(R.string.foreground_notification_channel_name);
|
||||||
|
String description = getString(R.string.foreground_notification_channel_name);
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
channel.setDescription(description);
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,4 +63,9 @@ public class TaskerScriptEditActivity extends BaseActivity {
|
|||||||
TaskerScriptEditActivity.super.finish();
|
TaskerScriptEditActivity.super.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
mEditorView.destroy();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,4 +161,7 @@ public class AutoCompletion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void shutdown(){
|
||||||
|
mExecutorService.shutdownNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ public class ScriptOperations {
|
|||||||
String fileName = DownloadManager.parseFileNameLocally(url);
|
String fileName = DownloadManager.parseFileNameLocally(url);
|
||||||
return new FileChooserDialogBuilder(mContext)
|
return new FileChooserDialogBuilder(mContext)
|
||||||
.title(R.string.text_select_save_path)
|
.title(R.string.text_select_save_path)
|
||||||
|
.dir(Pref.getScriptDirPath())
|
||||||
.chooseDir()
|
.chooseDir()
|
||||||
.singleChoice()
|
.singleChoice()
|
||||||
.map(saveDir -> new File(saveDir, fileName).getPath())
|
.map(saveDir -> new File(saveDir, fileName).getPath())
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
|
mEditorView.destroy();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
|
|||||||
Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
ScriptExecution execution = Scripts.runWithBroadcastSender(mFile);
|
ScriptExecution execution = Scripts.runWithBroadcastSender(mFile);
|
||||||
if(execution == null){
|
if (execution == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
mScriptExecutionId = execution.getId();
|
mScriptExecutionId = execution.getId();
|
||||||
@@ -671,4 +671,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
|
|||||||
super.onRestoreInstanceState(superData);
|
super.onRestoreInstanceState(superData);
|
||||||
setMenuItemStatus(R.id.run, mScriptExecutionId == ScriptExecution.NO_ID);
|
setMenuItemStatus(R.id.run, mScriptExecutionId == ScriptExecution.NO_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
mEditor.destroy();
|
||||||
|
mAutoCompletion.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,11 @@ public class CodeEditor extends HVScrollView {
|
|||||||
mCodeEditText.removeAllBreakpoints();
|
mCodeEditText.removeAllBreakpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroy(){
|
||||||
|
mJavaScriptHighlighter.shutdown();
|
||||||
|
mJsBeautifier.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package org.autojs.autojs.ui.edit.editor;
|
|||||||
|
|
||||||
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.util.TimingLogger;
|
import android.util.TimingLogger;
|
||||||
|
|
||||||
import com.stardust.autojs.rhino.TokenStream;
|
import com.stardust.autojs.rhino.TokenStream;
|
||||||
import com.stardust.pio.UncheckedIOException;
|
import com.stardust.pio.UncheckedIOException;
|
||||||
|
|
||||||
import org.autojs.autojs.ui.edit.theme.Theme;
|
import org.autojs.autojs.ui.edit.theme.Theme;
|
||||||
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
|
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + "{count = " + mCount + ", length = " + mText.length() + "}";
|
return super.toString() + "{count = " + mCount + ", length = " + mText.length() + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCharCount() {
|
public int getCharCount() {
|
||||||
@@ -60,16 +62,18 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
|
|
||||||
private Theme mTheme;
|
private Theme mTheme;
|
||||||
private CodeEditText mCodeEditText;
|
private CodeEditText mCodeEditText;
|
||||||
private ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 6,
|
private ThreadPoolExecutor mExecutorService = new ThreadPoolExecutor(3, 6,
|
||||||
2L, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
|
2L, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
|
||||||
private AtomicInteger mRunningHighlighterId = new AtomicInteger();
|
private AtomicInteger mRunningHighlighterId = new AtomicInteger();
|
||||||
private TimingLogger mLogger = new TimingLogger(CodeEditText.LOG_TAG, "highlight");
|
private TimingLogger mLogger = new TimingLogger(CodeEditText.LOG_TAG, "highlight");
|
||||||
|
private final TextWatcher mTextWatcher;
|
||||||
|
|
||||||
public JavaScriptHighlighter(Theme theme, CodeEditText codeEditText) {
|
public JavaScriptHighlighter(Theme theme, CodeEditText codeEditText) {
|
||||||
mExecutorService.allowCoreThreadTimeOut(true);
|
mExecutorService.allowCoreThreadTimeOut(true);
|
||||||
mTheme = theme;
|
mTheme = theme;
|
||||||
mCodeEditText = codeEditText;
|
mCodeEditText = codeEditText;
|
||||||
codeEditText.addTextChangedListener(new SimpleTextWatcher(this));
|
mTextWatcher = new SimpleTextWatcher(this);
|
||||||
|
codeEditText.addTextChangedListener(mTextWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,5 +116,9 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
mCodeEditText.updateHighlightTokens(highlightTokens);
|
mCodeEditText.updateHighlightTokens(highlightTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
mCodeEditText.removeTextChangedListener(mTextWatcher);
|
||||||
|
mExecutorService.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,290 +0,0 @@
|
|||||||
package org.autojs.autojs.ui.edit.editor;
|
|
||||||
/*
|
|
||||||
* Copyright 2016. SHENQINCI(沈钦赐)<946736079@qq.com>
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销和恢复撤销
|
|
||||||
* Created by 沈钦赐 on 16/6/23.
|
|
||||||
*/
|
|
||||||
public class TextViewRedoUndoer {
|
|
||||||
//操作序号(一次编辑可能对应多个操作,如替换文字,就是删除+插入)
|
|
||||||
int index;
|
|
||||||
//撤销栈
|
|
||||||
Stack<Action> history = new Stack<>();
|
|
||||||
//恢复栈
|
|
||||||
Stack<Action> historyBack = new Stack<>();
|
|
||||||
|
|
||||||
private Editable editable;
|
|
||||||
private EditText editText;
|
|
||||||
//自动操作标志,防止重复回调,导致无限撤销
|
|
||||||
private boolean flag = false;
|
|
||||||
|
|
||||||
private int mInitialHistoryStackSize;
|
|
||||||
private boolean mEnabled = true;
|
|
||||||
|
|
||||||
public TextViewRedoUndoer(@NonNull EditText editText) {
|
|
||||||
this.editable = editText.getText();
|
|
||||||
this.editText = editText;
|
|
||||||
editText.addTextChangedListener(new Watcher());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onEditableChanged(Editable s) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onTextChanged(Editable s) {
|
|
||||||
if (history.size() < mInitialHistoryStackSize) {
|
|
||||||
mInitialHistoryStackSize = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理记录
|
|
||||||
* Clear history.
|
|
||||||
*/
|
|
||||||
public final void clearHistory() {
|
|
||||||
history.clear();
|
|
||||||
historyBack.clear();
|
|
||||||
mInitialHistoryStackSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canUndo() {
|
|
||||||
return !history.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销
|
|
||||||
* Undo.
|
|
||||||
*/
|
|
||||||
public final void undo() {
|
|
||||||
if (history.empty()) return;
|
|
||||||
//锁定操作
|
|
||||||
flag = true;
|
|
||||||
Action action = history.pop();
|
|
||||||
historyBack.push(action);
|
|
||||||
if (action.isAdd) {
|
|
||||||
//撤销添加
|
|
||||||
editable.delete(action.startCursor, action.startCursor + action.actionTarget.length());
|
|
||||||
editText.setSelection(action.startCursor, action.startCursor);
|
|
||||||
} else {
|
|
||||||
//插销删除
|
|
||||||
editable.insert(action.startCursor, action.actionTarget);
|
|
||||||
if (action.endCursor == action.startCursor) {
|
|
||||||
editText.setSelection(action.startCursor + action.actionTarget.length());
|
|
||||||
} else {
|
|
||||||
editText.setSelection(action.startCursor, action.endCursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//释放操作
|
|
||||||
flag = false;
|
|
||||||
//判断是否是下一个动作是否和本动作是同一个操作,直到不同为止
|
|
||||||
if (!history.empty() && history.peek().index == action.index) {
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canRedo() {
|
|
||||||
return !historyBack.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复
|
|
||||||
* Redo.
|
|
||||||
*/
|
|
||||||
public final void redo() {
|
|
||||||
if (historyBack.empty()) return;
|
|
||||||
flag = true;
|
|
||||||
Action action = historyBack.pop();
|
|
||||||
history.push(action);
|
|
||||||
if (action.isAdd) {
|
|
||||||
//恢复添加
|
|
||||||
editable.insert(action.startCursor, action.actionTarget);
|
|
||||||
if (action.endCursor == action.startCursor) {
|
|
||||||
editText.setSelection(action.startCursor + action.actionTarget.length());
|
|
||||||
} else {
|
|
||||||
editText.setSelection(action.startCursor, action.endCursor);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//恢复删除
|
|
||||||
editable.delete(action.startCursor, action.startCursor + action.actionTarget.length());
|
|
||||||
editText.setSelection(action.startCursor, action.startCursor);
|
|
||||||
}
|
|
||||||
flag = false;
|
|
||||||
//判断是否是下一个动作是否和本动作是同一个操作
|
|
||||||
if (!historyBack.empty() && historyBack.peek().index == action.index)
|
|
||||||
redo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 首次设置文本
|
|
||||||
* Set default text.
|
|
||||||
*/
|
|
||||||
public final void setDefaultText(CharSequence text) {
|
|
||||||
clearHistory();
|
|
||||||
flag = true;
|
|
||||||
editable.replace(0, editable.length(), text);
|
|
||||||
flag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTextChanged() {
|
|
||||||
return history.size() != mInitialHistoryStackSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markTextAsUnchanged() {
|
|
||||||
mInitialHistoryStackSize = history.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return mEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Watcher implements TextWatcher {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Before text changed.
|
|
||||||
*
|
|
||||||
* @param s the s
|
|
||||||
* @param start the start 起始光标
|
|
||||||
* @param count the endCursor 选择数量
|
|
||||||
* @param after the after 替换增加的文字数
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
if (!editText.isEnabled() || !mEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (flag) return;
|
|
||||||
int end = start + count;
|
|
||||||
if (end > start && end <= s.length()) {
|
|
||||||
CharSequence charSequence = s.subSequence(start, end);
|
|
||||||
//删除了文字
|
|
||||||
if (charSequence.length() > 0) {
|
|
||||||
Action action = new Action(charSequence, start, false);
|
|
||||||
if (count > 1) {
|
|
||||||
//如果一次超过一个字符,说名用户选择了,然后替换或者删除操作
|
|
||||||
action.setSelectCount(count);
|
|
||||||
} else if (count == 1 && count == after) {
|
|
||||||
//一个字符替换
|
|
||||||
action.setSelectCount(count);
|
|
||||||
}
|
|
||||||
//还有一种情况:选择一个字符,然后删除(暂时没有考虑这种情况)
|
|
||||||
|
|
||||||
history.push(action);
|
|
||||||
historyBack.clear();
|
|
||||||
action.setIndex(++index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On text changed.
|
|
||||||
*
|
|
||||||
* @param s the s
|
|
||||||
* @param start the start 起始光标
|
|
||||||
* @param before the before 选择数量
|
|
||||||
* @param count the endCursor 添加的数量
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
if (!editText.isEnabled() || !mEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (flag) return;
|
|
||||||
int end = start + count;
|
|
||||||
if (end > start) {
|
|
||||||
CharSequence charSequence = s.subSequence(start, end);
|
|
||||||
//添加文字
|
|
||||||
if (charSequence.length() > 0) {
|
|
||||||
Action action = new Action(charSequence, start, true);
|
|
||||||
|
|
||||||
history.push(action);
|
|
||||||
historyBack.clear();
|
|
||||||
if (before > 0) {
|
|
||||||
//文字替换(先删除再增加),删除和增加是同一个操作,所以不需要增加序号
|
|
||||||
action.setIndex(index);
|
|
||||||
} else {
|
|
||||||
action.setIndex(++index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void afterTextChanged(Editable s) {
|
|
||||||
if (!editText.isEnabled() || !mEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (flag) return;
|
|
||||||
if (s != editable) {
|
|
||||||
editable = s;
|
|
||||||
onEditableChanged(s);
|
|
||||||
}
|
|
||||||
TextViewRedoUndoer.this.onTextChanged(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Action {
|
|
||||||
/**
|
|
||||||
* 改变字符.
|
|
||||||
*/
|
|
||||||
CharSequence actionTarget;
|
|
||||||
/**
|
|
||||||
* 光标位置.
|
|
||||||
*/
|
|
||||||
int startCursor;
|
|
||||||
int endCursor;
|
|
||||||
/**
|
|
||||||
* 标志增加操作.
|
|
||||||
*/
|
|
||||||
boolean isAdd;
|
|
||||||
/**
|
|
||||||
* 操作序号.
|
|
||||||
*/
|
|
||||||
int index;
|
|
||||||
|
|
||||||
|
|
||||||
public Action(CharSequence actionTag, int startCursor, boolean add) {
|
|
||||||
this.actionTarget = actionTag;
|
|
||||||
this.startCursor = startCursor;
|
|
||||||
this.endCursor = startCursor;
|
|
||||||
this.isAdd = add;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectCount(int count) {
|
|
||||||
this.endCursor = endCursor + count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndex(int index) {
|
|
||||||
this.index = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ import java.util.LinkedList;
|
|||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.os.Handler;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Selection;
|
import android.text.Selection;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
@@ -46,6 +47,10 @@ public class TextViewUndoRedo {
|
|||||||
|
|
||||||
private int mInitialHistoryStackSize;
|
private int mInitialHistoryStackSize;
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler();
|
||||||
|
private int mTextChangeId = 0;
|
||||||
|
private boolean mTextChanging = false;
|
||||||
|
|
||||||
// =================================================================== //
|
// =================================================================== //
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -404,7 +409,6 @@ public class TextViewUndoRedo {
|
|||||||
if (mIsUndoOrRedo || !mEnabled) {
|
if (mIsUndoOrRedo || !mEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mBeforeChange = s.subSequence(start, start + count);
|
mBeforeChange = s.subSequence(start, start + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +419,10 @@ public class TextViewUndoRedo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mAfterChange = s.subSequence(start, start + count);
|
mAfterChange = s.subSequence(start, start + count);
|
||||||
|
mTextChangeId++;
|
||||||
mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange));
|
mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange));
|
||||||
|
int textChangeId = mTextChangeId;
|
||||||
|
//TODO 增加连续输入文字当成一次撤销的功能
|
||||||
}
|
}
|
||||||
|
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.autojs.autojs.ui.main.drawer;
|
package org.autojs.autojs.ui.main.drawer;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -22,6 +23,7 @@ import com.stardust.notification.NotificationListenerService;
|
|||||||
|
|
||||||
import org.autojs.autojs.Pref;
|
import org.autojs.autojs.Pref;
|
||||||
import org.autojs.autojs.R;
|
import org.autojs.autojs.R;
|
||||||
|
import org.autojs.autojs.external.foreground.ForegroundService;
|
||||||
import org.autojs.autojs.network.GlideApp;
|
import org.autojs.autojs.network.GlideApp;
|
||||||
import org.autojs.autojs.network.UserService;
|
import org.autojs.autojs.network.UserService;
|
||||||
import org.autojs.autojs.tool.EmptyObservers;
|
import org.autojs.autojs.tool.EmptyObservers;
|
||||||
@@ -107,6 +109,8 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private DrawerMenuItem mNotificationPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_notification_permission, 0, this::goToNotificationServiceSettings);
|
private DrawerMenuItem mNotificationPermissionItem = new DrawerMenuItem(R.drawable.ic_ali_notification, R.string.text_notification_permission, 0, this::goToNotificationServiceSettings);
|
||||||
|
private DrawerMenuItem mForegroundServiceItem = new DrawerMenuItem(R.drawable.ic_service_green, R.string.text_foreground_service, R.string.key_foreground_servie, this::toggleForegroundService);
|
||||||
|
|
||||||
private DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
|
private DrawerMenuItem mFloatingWindowItem = new DrawerMenuItem(R.drawable.ic_robot_64, R.string.text_floating_window, 0, this::showOrDismissFloatingWindow);
|
||||||
private DrawerMenuItem mCheckForUpdatesItem = new DrawerMenuItem(R.drawable.ic_check_for_updates, R.string.text_check_for_updates, this::checkForUpdates);
|
private DrawerMenuItem mCheckForUpdatesItem = new DrawerMenuItem(R.drawable.ic_check_for_updates, R.string.text_check_for_updates, this::checkForUpdates);
|
||||||
|
|
||||||
@@ -142,6 +146,10 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
|
|||||||
setChecked(mFloatingWindowItem, true);
|
setChecked(mFloatingWindowItem, true);
|
||||||
}
|
}
|
||||||
setChecked(mConnectionItem, DevPluginService.getInstance().isConnected());
|
setChecked(mConnectionItem, DevPluginService.getInstance().isConnected());
|
||||||
|
if(Pref.isForegroundServiceEnabled()){
|
||||||
|
GlobalAppContext.get().startService(new Intent(getContext(), ForegroundService.class));
|
||||||
|
setChecked(mForegroundServiceItem, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMenuItems() {
|
private void initMenuItems() {
|
||||||
@@ -150,6 +158,7 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
|
|||||||
mAccessibilityServiceItem,
|
mAccessibilityServiceItem,
|
||||||
mStableModeItem,
|
mStableModeItem,
|
||||||
mNotificationPermissionItem,
|
mNotificationPermissionItem,
|
||||||
|
mForegroundServiceItem,
|
||||||
|
|
||||||
new DrawerMenuGroup(R.string.text_script_record),
|
new DrawerMenuGroup(R.string.text_script_record),
|
||||||
mFloatingWindowItem,
|
mFloatingWindowItem,
|
||||||
@@ -165,6 +174,7 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
@Click(R.id.avatar)
|
@Click(R.id.avatar)
|
||||||
void loginOrShowUserInfo() {
|
void loginOrShowUserInfo() {
|
||||||
UserService.getInstance()
|
UserService.getInstance()
|
||||||
@@ -251,6 +261,17 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void toggleForegroundService(DrawerMenuItemViewHolder holder) {
|
||||||
|
boolean checked = holder.getSwitchCompat().isChecked();
|
||||||
|
if(checked){
|
||||||
|
GlobalAppContext.get().startService(new Intent(getContext(), ForegroundService.class));
|
||||||
|
}else {
|
||||||
|
GlobalAppContext.get().stopService(new Intent(getContext(), ForegroundService.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void inputRemoteHost() {
|
private void inputRemoteHost() {
|
||||||
String host = Pref.getServerAddressOrDefault(WifiTool.getRouterIp(getActivity()));
|
String host = Pref.getServerAddressOrDefault(WifiTool.getRouterIp(getActivity()));
|
||||||
new MaterialDialog.Builder(getActivity())
|
new MaterialDialog.Builder(getActivity())
|
||||||
|
|||||||
@@ -398,4 +398,9 @@
|
|||||||
<string name="text_new_project">新建项目</string>
|
<string name="text_new_project">新建项目</string>
|
||||||
<string name="text_js_file">js文件</string>
|
<string name="text_js_file">js文件</string>
|
||||||
<string name="text_invalid_project">无效项目</string>
|
<string name="text_invalid_project">无效项目</string>
|
||||||
|
<string name="text_foreground_service">前台服务</string>
|
||||||
|
<string name="key_foreground_servie">key_foreground_service</string>
|
||||||
|
<string name="foreground_notification_channel_name">前台服务通知</string>
|
||||||
|
<string name="foreground_notification_title">Auto.js保持运行中</string>
|
||||||
|
<string name="foreground_notification_text">点击进入主界面</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.stardust.autojs.engine.ScriptEngine;
|
|||||||
import com.stardust.autojs.engine.ScriptEngineManager;
|
import com.stardust.autojs.engine.ScriptEngineManager;
|
||||||
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
|
import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
|
||||||
import com.stardust.autojs.script.ScriptSource;
|
import com.stardust.autojs.script.ScriptSource;
|
||||||
|
import com.stardust.lang.ThreadCompat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on 2017/5/1.
|
* Created by Stardust on 2017/5/1.
|
||||||
@@ -24,6 +25,7 @@ public class RunnableScriptExecution extends ScriptExecution.AbstractScriptExecu
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
ThreadCompat.currentThread().setName("ScriptThread-" + getId() + "[" + getSource() + "]");
|
||||||
execute();
|
execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.stardust.autojs.rhino;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.dx.command.dexer.Main;
|
import com.android.dx.command.dexer.Main;
|
||||||
|
import com.android.dx.dex.file.DexFile;
|
||||||
|
|
||||||
import net.lingala.zip4j.core.ZipFile;
|
import net.lingala.zip4j.core.ZipFile;
|
||||||
import net.lingala.zip4j.exception.ZipException;
|
import net.lingala.zip4j.exception.ZipException;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class Threads {
|
|||||||
TimerThread thread = createThread(runnable);
|
TimerThread thread = createThread(runnable);
|
||||||
synchronized (mThreads) {
|
synchronized (mThreads) {
|
||||||
mThreads.add(thread);
|
mThreads.add(thread);
|
||||||
thread.setName(thread.getName() + " (Spawn-" + mSpawnCount + ")");
|
thread.setName(mMainThread.getName() + " (Spawn-" + mSpawnCount + ")");
|
||||||
mSpawnCount++;
|
mSpawnCount++;
|
||||||
}
|
}
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +36,7 @@ public class JsBeautifier {
|
|||||||
void onException(Exception e);
|
void onException(Exception e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Executor mExecutor = Executors.newSingleThreadExecutor();
|
private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private Function mJsBeautifyFunction;
|
private Function mJsBeautifyFunction;
|
||||||
private org.mozilla.javascript.Context mScriptContext;
|
private org.mozilla.javascript.Context mScriptContext;
|
||||||
@@ -122,4 +123,9 @@ public class JsBeautifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown(){
|
||||||
|
mExecutor.shutdownNow();
|
||||||
|
mView = null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":416},"path":"commonRelease-4.0.2 Alpha11.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
|
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":421},"path":"commonRelease-4.0.3 Alpha2.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
|
||||||
Reference in New Issue
Block a user