fix: sensors's callback persisted in loopers' ThreadLocal, causes ScriptExecuteActivity memory leak

This commit is contained in:
hyb1996 2018-09-19 20:28:53 +08:00
parent 16709529e3
commit bb130f56ea
10 changed files with 68 additions and 45 deletions

View File

@ -91,7 +91,7 @@ public class App extends MultiDexApplication {
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
//LeakCanary.install(this);
}
private void init() {

View File

@ -65,6 +65,7 @@ public abstract class AutoJs {
mNotificationObserver = new AccessibilityNotificationObserver(mContext);
mAccessibilityInfoProvider = new AccessibilityInfoProvider(mContext.getPackageManager());
mScriptEngineService = buildScriptEngineService();
ScriptEngineService.setInstance(mScriptEngineService);
init();
}

View File

@ -67,9 +67,7 @@ public class ScriptEngineService {
}
private void onFinish(ScriptExecution execution) {
if (execution.getEngine() instanceof JavaScriptEngine) {
((JavaScriptEngine) execution.getEngine()).getRuntime().onExit();
}
}
@Override
@ -87,6 +85,7 @@ public class ScriptEngineService {
};
private static ScriptEngineService sInstance;
private final Context mContext;
private UiHandler mUiHandler;
private final Console mGlobalConsole;
@ -211,6 +210,18 @@ public class ScriptEngineService {
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 final Set<ScriptEngineManager.EngineLifecycleCallback> mEngineLifecycleCallbacks = new LinkedHashSet<>();

View File

@ -5,7 +5,6 @@ import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import com.android.dx.util.IntSet;
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.runtime.api.Threads;
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<HashSet<Integer>> waitIds = 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 Timers mTimers;
private ScriptRuntime mScriptRuntime;
@ -58,17 +57,17 @@ public class Loopers implements MessageQueue.IdleHandler {
return mMainLooper;
}
public void addLooperQuiteHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
public void addLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
handlers = new CopyOnWriteArrayList<>();
looperQuitHanders.set(handlers);
looperQuitHandlers.set(handlers);
}
handlers.add(handler);
}
public boolean removeLooperQuiteHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
public boolean removeLooperQuitHandler(LooperQuitHandler handler) {
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
return handlers != null && handlers.remove(handler);
}
@ -82,7 +81,7 @@ public class Loopers implements MessageQueue.IdleHandler {
if (waitWhenIdle.get() || !waitIds.get().isEmpty()) {
return false;
}
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHanders.get();
CopyOnWriteArrayList<LooperQuitHandler> handlers = looperQuitHandlers.get();
if (handlers == null) {
return true;
}

View File

@ -45,6 +45,12 @@ public abstract class JavaScriptEngine extends ScriptEngine.AbstractScriptEngine
return (ScriptSource) getTag(TAG_SOURCE);
}
@Override
public synchronized void destroy() {
mRuntime.onExit();
super.destroy();
}
@Override
public String toString() {
return "ScriptEngine@" + Integer.toHexString(hashCode()) + "{" +

View File

@ -5,6 +5,7 @@ import android.support.annotation.CallSuper;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.ScriptSource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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> {
private Map<String, Object> mTags = new ConcurrentHashMap<>();
private Map<String, Object> mTags = new HashMap<>();
private OnDestroyListener mOnDestroyListener;
private boolean mDestroyed = false;
private Exception mUncaughtException;
@ -76,9 +77,11 @@ public interface ScriptEngine<S extends ScriptSource> {
@Override
public synchronized void setTag(String key, Object value) {
if (value == null)
return;
mTags.put(key, value);
if (value == null) {
mTags.remove(key);
} else {
mTags.put(key, value);
}
}
@Override

View File

@ -7,11 +7,13 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.core.eventloop.EventEmitter;
import com.stardust.autojs.core.eventloop.SimpleEvent;
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.api.UI;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.util.IntentExtras;
import org.mozilla.javascript.NativeObject;
@ -32,13 +33,13 @@ import org.mozilla.javascript.NativeObject;
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 ScriptEngine mScriptEngine;
private ScriptExecutionListener mExecutionListener;
private ScriptSource mScriptSource;
private ActivityScriptExecution mScriptExecution;
private IntentExtras mIntentExtras;
private ScriptRuntime mRuntime;
@ -47,10 +48,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
public static ActivityScriptExecution execute(Context context, ScriptEngineManager manager, ScriptExecutionTask task) {
ActivityScriptExecution execution = new ActivityScriptExecution(manager, task);
Intent i = new Intent(context, ScriptExecuteActivity.class)
.putExtra(EXTRA_EXECUTION_ID, execution.getId())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentExtras.newExtras()
.put(EXTRA_EXECUTION, execution)
.putInIntent(i);
context.startActivity(i);
return execution;
}
@ -59,36 +58,30 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntentExtras = readIntentExtras(savedInstanceState);
if (mIntentExtras == null || mIntentExtras.get(EXTRA_EXECUTION) == null) {
finish();
int executionId = getIntent().getIntExtra(EXTRA_EXECUTION_ID, ScriptExecution.NO_ID);
if (executionId == ScriptExecution.NO_ID) {
super.finish();
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();
mScriptEngine = mScriptExecution.createEngine(this);
mExecutionListener = mScriptExecution.getListener();
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
mEventEmitter = new EventEmitter(mRuntime.bridges);
runScript();
emit("create", savedInstanceState);
}
public EventEmitter getEventEmitter() {
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() {
try {
prepare();
@ -130,6 +123,10 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
public void finish() {
if (mScriptExecution == null || mExecutionListener == null) {
super.finish();
return;
}
Exception exception = mScriptEngine.getUncaughtException();
if (exception != null) {
onException(exception);
@ -142,6 +139,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
mScriptEngine.put("activity", null);
mScriptEngine.setTag("activity", null);
mScriptEngine.destroy();
@ -151,10 +149,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mIntentExtras == null)
return;
IntentExtras extras = IntentExtras.newExtras().putAll(mIntentExtras);
outState.putInt(IntentExtras.EXTRA_ID, extras.getId());
if (mScriptExecution != null)
outState.putInt(EXTRA_EXECUTION_ID, mScriptExecution.getId());
emit("save_instance_state", outState);
}

View File

@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.R;
@ -333,7 +334,7 @@ public class ScriptRuntime {
}
}
public void loadDex(String path){
public void loadDex(String path) {
path = files.path(path);
try {
((AndroidClassLoader) ContextFactory.getGlobal().getApplicationClassLoader()).loadDex(new File(path));
@ -375,6 +376,7 @@ public class ScriptRuntime {
}
public void onExit() {
Log.d(TAG, "on exit");
//清除interrupt状态
ThreadCompat.interrupted();
//悬浮窗需要第一时间关闭以免出现恶意脚本全屏悬浮窗屏蔽屏幕并且在exit中写死循环的问题
@ -398,6 +400,7 @@ public class ScriptRuntime {
}
ignoresException(sensors::unregisterAll);
ignoresException(timers::recycle);
ignoresException(ui::recycle);
}
private void ignoresException(Runnable r) {

View File

@ -17,7 +17,6 @@ import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by Stardust on 2018/2/5.
@ -92,7 +91,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
mScriptBridges = runtime.bridges;
mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges);
mScriptRuntime = runtime;
runtime.loopers.addLooperQuiteHandler(this);
runtime.loopers.addLooperQuitHandler(this);
}
public SensorEventEmitter register(String sensorName) {
@ -167,5 +166,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler {
}
mSensorEventEmitters.clear();
}
mScriptRuntime.loopers.removeLooperQuitHandler(this);
}
}

View File

@ -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 {
@Override