fix: sensors's callback persisted in loopers' ThreadLocal, causes ScriptExecuteActivity memory leak
This commit is contained in:
@@ -91,7 +91,7 @@ public class App extends MultiDexApplication {
|
|||||||
// You should not init your app in this process.
|
// You should not init your app in this process.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LeakCanary.install(this);
|
//LeakCanary.install(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public abstract class AutoJs {
|
|||||||
mNotificationObserver = new AccessibilityNotificationObserver(mContext);
|
mNotificationObserver = new AccessibilityNotificationObserver(mContext);
|
||||||
mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext.getPackageManager());
|
mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext.getPackageManager());
|
||||||
mScriptEngineService = buildScriptEngineService();
|
mScriptEngineService = buildScriptEngineService();
|
||||||
|
ScriptEngineService.setInstance(mScriptEngineService);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ public class ScriptEngineService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onFinish(ScriptExecution execution) {
|
private void onFinish(ScriptExecution execution) {
|
||||||
if (execution.getEngine() instanceof JavaScriptEngine) {
|
|
||||||
((JavaScriptEngine) execution.getEngine()).getRuntime().onExit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -87,6 +85,7 @@ public class ScriptEngineService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static ScriptEngineService sInstance;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private UiHandler mUiHandler;
|
private UiHandler mUiHandler;
|
||||||
private final Console mGlobalConsole;
|
private final Console mGlobalConsole;
|
||||||
@@ -211,6 +210,18 @@ public class ScriptEngineService {
|
|||||||
return mScriptExecutions.get(id);
|
return mScriptExecutions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setInstance(ScriptEngineService service) {
|
||||||
|
if (sInstance != null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
sInstance = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScriptEngineService getInstance() {
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class EngineLifecycleObserver implements ScriptEngineManager.EngineLifecycleCallback {
|
private static class EngineLifecycleObserver implements ScriptEngineManager.EngineLifecycleCallback {
|
||||||
|
|
||||||
private final Set<ScriptEngineManager.EngineLifecycleCallback> mEngineLifecycleCallbacks = new LinkedHashSet<>();
|
private final Set<ScriptEngineManager.EngineLifecycleCallback> mEngineLifecycleCallbacks = new LinkedHashSet<>();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.os.Looper;
|
|||||||
import android.os.MessageQueue;
|
import android.os.MessageQueue;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.dx.util.IntSet;
|
|
||||||
import com.stardust.autojs.runtime.ScriptRuntime;
|
import com.stardust.autojs.runtime.ScriptRuntime;
|
||||||
import com.stardust.autojs.runtime.api.Threads;
|
import com.stardust.autojs.runtime.api.Threads;
|
||||||
import com.stardust.autojs.runtime.api.Timers;
|
import com.stardust.autojs.runtime.api.Timers;
|
||||||
@@ -33,7 +32,7 @@ public class Loopers implements MessageQueue.IdleHandler {
|
|||||||
private volatile ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>();
|
private volatile ThreadLocal<Boolean> waitWhenIdle = new ThreadLocal<>();
|
||||||
private volatile ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>();
|
private volatile ThreadLocal<HashSet<Integer>> waitIds = new ThreadLocal<>();
|
||||||
private volatile ThreadLocal<Integer> maxWaitId = new ThreadLocal<>();
|
private volatile ThreadLocal<Integer> maxWaitId = new ThreadLocal<>();
|
||||||
private volatile ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHanders = new ThreadLocal<>();
|
private volatile ThreadLocal<CopyOnWriteArrayList<LooperQuitHandler>> looperQuitHandlers = new ThreadLocal<>();
|
||||||
private volatile Looper mServantLooper;
|
private volatile Looper mServantLooper;
|
||||||
private Timers mTimers;
|
private Timers mTimers;
|
||||||
private ScriptRuntime mScriptRuntime;
|
private ScriptRuntime mScriptRuntime;
|
||||||
@@ -58,17 +57,17 @@ public class Loopers implements MessageQueue.IdleHandler {
|
|||||||
return mMainLooper;
|
return mMainLooper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLooperQuiteHandler(LooperQuitHandler handler) {
|
public void addLooperQuitHandler(LooperQuitHandler handler) {
|
||||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
|
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||||
if (handlers == null) {
|
if (handlers == null) {
|
||||||
handlers = new CopyOnWriteArrayList<>();
|
handlers = new CopyOnWriteArrayList<>();
|
||||||
looperQuitHanders.set(handlers);
|
looperQuitHandlers.set(handlers);
|
||||||
}
|
}
|
||||||
handlers.add(handler);
|
handlers.add(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeLooperQuiteHandler(LooperQuitHandler handler) {
|
public boolean removeLooperQuitHandler(LooperQuitHandler handler) {
|
||||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
|
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||||
return handlers != null && handlers.remove(handler);
|
return handlers != null && handlers.remove(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ public class Loopers implements MessageQueue.IdleHandler {
|
|||||||
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
|
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
|
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
|
||||||
if (handlers == null) {
|
if (handlers == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ public abstract class JavaScriptEngine extends ScriptEngine.AbstractScriptEngine
|
|||||||
return (ScriptSource) getTag(TAG_SOURCE);
|
return (ScriptSource) getTag(TAG_SOURCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void destroy() {
|
||||||
|
mRuntime.onExit();
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ScriptEngine@" + Integer.toHexString(hashCode()) + "{" +
|
return "ScriptEngine@" + Integer.toHexString(hashCode()) + "{" +
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.support.annotation.CallSuper;
|
|||||||
import com.stardust.autojs.execution.ScriptExecution;
|
import com.stardust.autojs.execution.ScriptExecution;
|
||||||
import com.stardust.autojs.script.ScriptSource;
|
import com.stardust.autojs.script.ScriptSource;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -68,7 +69,7 @@ public interface ScriptEngine<S extends ScriptSource> {
|
|||||||
abstract class AbstractScriptEngine<S extends ScriptSource> implements ScriptEngine<S> {
|
abstract class AbstractScriptEngine<S extends ScriptSource> implements ScriptEngine<S> {
|
||||||
|
|
||||||
|
|
||||||
private Map<String, Object> mTags = new ConcurrentHashMap<>();
|
private Map<String, Object> mTags = new HashMap<>();
|
||||||
private OnDestroyListener mOnDestroyListener;
|
private OnDestroyListener mOnDestroyListener;
|
||||||
private boolean mDestroyed = false;
|
private boolean mDestroyed = false;
|
||||||
private Exception mUncaughtException;
|
private Exception mUncaughtException;
|
||||||
@@ -76,10 +77,12 @@ public interface ScriptEngine<S extends ScriptSource> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void setTag(String key, Object value) {
|
public synchronized void setTag(String key, Object value) {
|
||||||
if (value == null)
|
if (value == null) {
|
||||||
return;
|
mTags.remove(key);
|
||||||
|
} else {
|
||||||
mTags.put(key, value);
|
mTags.put(key, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized Object getTag(String key) {
|
public synchronized Object getTag(String key) {
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import com.stardust.autojs.ScriptEngineService;
|
||||||
import com.stardust.autojs.core.eventloop.EventEmitter;
|
import com.stardust.autojs.core.eventloop.EventEmitter;
|
||||||
import com.stardust.autojs.core.eventloop.SimpleEvent;
|
import com.stardust.autojs.core.eventloop.SimpleEvent;
|
||||||
import com.stardust.autojs.engine.JavaScriptEngine;
|
import com.stardust.autojs.engine.JavaScriptEngine;
|
||||||
@@ -21,7 +23,6 @@ import com.stardust.autojs.engine.ScriptEngineManager;
|
|||||||
import com.stardust.autojs.runtime.ScriptRuntime;
|
import com.stardust.autojs.runtime.ScriptRuntime;
|
||||||
import com.stardust.autojs.runtime.api.UI;
|
import com.stardust.autojs.runtime.api.UI;
|
||||||
import com.stardust.autojs.script.ScriptSource;
|
import com.stardust.autojs.script.ScriptSource;
|
||||||
import com.stardust.util.IntentExtras;
|
|
||||||
|
|
||||||
import org.mozilla.javascript.NativeObject;
|
import org.mozilla.javascript.NativeObject;
|
||||||
|
|
||||||
@@ -32,13 +33,13 @@ import org.mozilla.javascript.NativeObject;
|
|||||||
public class ScriptExecuteActivity extends AppCompatActivity {
|
public class ScriptExecuteActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
|
||||||
private static final String EXTRA_EXECUTION = ScriptExecuteActivity.class.getName() + ".execution";
|
private static final String LOG_TAG = "ScriptExecuteActivity";
|
||||||
|
private static final String EXTRA_EXECUTION_ID = ScriptExecuteActivity.class.getName() + ".execution_id";
|
||||||
private Object mResult;
|
private Object mResult;
|
||||||
private ScriptEngine mScriptEngine;
|
private ScriptEngine mScriptEngine;
|
||||||
private ScriptExecutionListener mExecutionListener;
|
private ScriptExecutionListener mExecutionListener;
|
||||||
private ScriptSource mScriptSource;
|
private ScriptSource mScriptSource;
|
||||||
private ActivityScriptExecution mScriptExecution;
|
private ActivityScriptExecution mScriptExecution;
|
||||||
private IntentExtras mIntentExtras;
|
|
||||||
private ScriptRuntime mRuntime;
|
private ScriptRuntime mRuntime;
|
||||||
|
|
||||||
|
|
||||||
@@ -47,10 +48,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
|||||||
public static ActivityScriptExecution execute(Context context, ScriptEngineManager manager, ScriptExecutionTask task) {
|
public static ActivityScriptExecution execute(Context context, ScriptEngineManager manager, ScriptExecutionTask task) {
|
||||||
ActivityScriptExecution execution = new ActivityScriptExecution(manager, task);
|
ActivityScriptExecution execution = new ActivityScriptExecution(manager, task);
|
||||||
Intent i = new Intent(context, ScriptExecuteActivity.class)
|
Intent i = new Intent(context, ScriptExecuteActivity.class)
|
||||||
|
.putExtra(EXTRA_EXECUTION_ID, execution.getId())
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
IntentExtras.newExtras()
|
|
||||||
.put(EXTRA_EXECUTION, execution)
|
|
||||||
.putInIntent(i);
|
|
||||||
context.startActivity(i);
|
context.startActivity(i);
|
||||||
return execution;
|
return execution;
|
||||||
}
|
}
|
||||||
@@ -59,36 +58,30 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mIntentExtras = readIntentExtras(savedInstanceState);
|
int executionId = getIntent().getIntExtra(EXTRA_EXECUTION_ID, ScriptExecution.NO_ID);
|
||||||
if (mIntentExtras == null || mIntentExtras.get(EXTRA_EXECUTION) == null) {
|
if (executionId == ScriptExecution.NO_ID) {
|
||||||
finish();
|
super.finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mScriptExecution = mIntentExtras.get(EXTRA_EXECUTION);
|
ScriptExecution execution = ScriptEngineService.getInstance().getScriptExecution(executionId);
|
||||||
|
if (execution == null || !(execution instanceof ActivityScriptExecution)) {
|
||||||
|
super.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mScriptExecution = (ActivityScriptExecution) execution;
|
||||||
mScriptSource = mScriptExecution.getSource();
|
mScriptSource = mScriptExecution.getSource();
|
||||||
mScriptEngine = mScriptExecution.createEngine(this);
|
mScriptEngine = mScriptExecution.createEngine(this);
|
||||||
mExecutionListener = mScriptExecution.getListener();
|
mExecutionListener = mScriptExecution.getListener();
|
||||||
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
|
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
|
||||||
mEventEmitter = new EventEmitter(mRuntime.bridges);
|
mEventEmitter = new EventEmitter(mRuntime.bridges);
|
||||||
runScript();
|
runScript();
|
||||||
|
emit("create", savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventEmitter getEventEmitter() {
|
public EventEmitter getEventEmitter() {
|
||||||
return mEventEmitter;
|
return mEventEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntentExtras readIntentExtras(Bundle savedInstanceState) {
|
|
||||||
IntentExtras extras = IntentExtras.fromIntentAndRelease(getIntent());
|
|
||||||
if (extras == null && savedInstanceState != null) {
|
|
||||||
int id = savedInstanceState.getInt(IntentExtras.EXTRA_ID, -1);
|
|
||||||
if (id == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
extras = IntentExtras.fromIdAndRelease(id);
|
|
||||||
}
|
|
||||||
return extras;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runScript() {
|
private void runScript() {
|
||||||
try {
|
try {
|
||||||
prepare();
|
prepare();
|
||||||
@@ -130,6 +123,10 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish() {
|
public void finish() {
|
||||||
|
if (mScriptExecution == null || mExecutionListener == null) {
|
||||||
|
super.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Exception exception = mScriptEngine.getUncaughtException();
|
Exception exception = mScriptEngine.getUncaughtException();
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
onException(exception);
|
onException(exception);
|
||||||
@@ -142,6 +139,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
Log.d(LOG_TAG, "onDestroy");
|
||||||
mScriptEngine.put("activity", null);
|
mScriptEngine.put("activity", null);
|
||||||
mScriptEngine.setTag("activity", null);
|
mScriptEngine.setTag("activity", null);
|
||||||
mScriptEngine.destroy();
|
mScriptEngine.destroy();
|
||||||
@@ -151,10 +149,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
if (mIntentExtras == null)
|
if (mScriptExecution != null)
|
||||||
return;
|
outState.putInt(EXTRA_EXECUTION_ID, mScriptExecution.getId());
|
||||||
IntentExtras extras = IntentExtras.newExtras().putAll(mIntentExtras);
|
|
||||||
outState.putInt(IntentExtras.EXTRA_ID, extras.getId());
|
|
||||||
emit("save_instance_state", outState);
|
emit("save_instance_state", outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import com.stardust.app.GlobalAppContext;
|
import com.stardust.app.GlobalAppContext;
|
||||||
import com.stardust.autojs.R;
|
import com.stardust.autojs.R;
|
||||||
@@ -375,6 +376,7 @@ public class ScriptRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onExit() {
|
public void onExit() {
|
||||||
|
Log.d(TAG, "on exit");
|
||||||
//清除interrupt状态
|
//清除interrupt状态
|
||||||
ThreadCompat.interrupted();
|
ThreadCompat.interrupted();
|
||||||
//悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题
|
//悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题
|
||||||
@@ -398,6 +400,7 @@ public class ScriptRuntime {
|
|||||||
}
|
}
|
||||||
ignoresException(sensors::unregisterAll);
|
ignoresException(sensors::unregisterAll);
|
||||||
ignoresException(timers::recycle);
|
ignoresException(timers::recycle);
|
||||||
|
ignoresException(ui::recycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ignoresException(Runnable r) {
|
private void ignoresException(Runnable r) {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Stardust on 2018/2/5.
|
* Created by Stardust on 2018/2/5.
|
||||||
@@ -92,7 +91,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
|||||||
mScriptBridges = runtime.bridges;
|
mScriptBridges = runtime.bridges;
|
||||||
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
|
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
|
||||||
mScriptRuntime = runtime;
|
mScriptRuntime = runtime;
|
||||||
runtime.loopers.addLooperQuiteHandler(this);
|
runtime.loopers.addLooperQuitHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorEventEmitter register(String sensorName) {
|
public SensorEventEmitter register(String sensorName) {
|
||||||
@@ -167,5 +166,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
|
|||||||
}
|
}
|
||||||
mSensorEventEmitters.clear();
|
mSensorEventEmitters.clear();
|
||||||
}
|
}
|
||||||
|
mScriptRuntime.loopers.removeLooperQuitHandler(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ public class UI extends ProxyObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void recycle(){
|
||||||
|
mDynamicLayoutInflater.setContext(null);
|
||||||
|
}
|
||||||
|
|
||||||
private class Drawables extends com.stardust.autojs.core.ui.inflater.util.Drawables {
|
private class Drawables extends com.stardust.autojs.core.ui.inflater.util.Drawables {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user