mirror of
https://github.com/TonyJiangWJ/Auto.js.git
synced 2026-06-24 21:33:16 +08:00
优化ScriptRuntime和TopLevelScope的资源回收
This commit is contained in:
parent
2205fd2f7a
commit
58ba1ff71a
@ -38,6 +38,7 @@ import com.stardust.autojs.core.ui.widget.JsSpinner;
|
||||
import com.stardust.autojs.core.ui.widget.JsTabLayout;
|
||||
import com.stardust.autojs.core.ui.widget.JsToolbar;
|
||||
import com.stardust.autojs.core.ui.xml.XmlConverter;
|
||||
import com.stardust.autojs.runtime.ScriptRuntime;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
@ -70,6 +71,7 @@ public class DynamicLayoutInflater {
|
||||
private LinkedList<WeakReference<View>> createdViews = new LinkedList<>();
|
||||
private Context mContext;
|
||||
private ResourceParser mResourceParser;
|
||||
private WeakReference<ScriptRuntime> mRuntime;
|
||||
@NonNull
|
||||
private LayoutInflaterDelegate mLayoutInflaterDelegate = LayoutInflaterDelegate.NO_OP;
|
||||
private int mInflateFlags;
|
||||
@ -87,6 +89,10 @@ public class DynamicLayoutInflater {
|
||||
this.mViewCreators = new HashMap<>(inflater.mViewCreators);
|
||||
}
|
||||
|
||||
public void setRuntime(ScriptRuntime runtime) {
|
||||
this.mRuntime = new WeakReference<>(runtime);
|
||||
}
|
||||
|
||||
public int getInflateFlags() {
|
||||
return mInflateFlags;
|
||||
}
|
||||
@ -188,6 +194,9 @@ public class DynamicLayoutInflater {
|
||||
try {
|
||||
return mLayoutInflaterDelegate.afterConvertXml(context, XmlConverter.convertToAndroidLayout(xml));
|
||||
} catch (Exception e) {
|
||||
if (mRuntime != null) {
|
||||
mRuntime.get().exit();
|
||||
}
|
||||
throw new InflateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
||||
|
||||
private val context: WeakReference<Context>
|
||||
private val mScriptable: WeakReference<TopLevelScope>
|
||||
private var unsealed: Boolean = false
|
||||
lateinit var threadRef: WeakReference<Thread>
|
||||
|
||||
private val initScript: Script
|
||||
@ -66,6 +67,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
||||
init {
|
||||
this.context = WeakReference(enterContext())
|
||||
mScriptable = this.context.get()?.let { createScope(it) }!!
|
||||
this.context.get()?.seal(mScriptable)
|
||||
}
|
||||
|
||||
override fun put(name: String, value: Any?) {
|
||||
@ -161,6 +163,10 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
||||
protected fun createScope(context: Context): WeakReference<TopLevelScope> {
|
||||
val topLevelScope = TopLevelScope()
|
||||
topLevelScope.initStandardObjects(context, false)
|
||||
if (unsealed) {
|
||||
// FIXME 会导致无法正常被回收 存在内存泄露可能
|
||||
topLevelScope.setNoRecycle()
|
||||
}
|
||||
return WeakReference(topLevelScope)
|
||||
}
|
||||
|
||||
@ -181,14 +187,16 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co
|
||||
}
|
||||
|
||||
protected fun setupContext(context: Context) {
|
||||
context.optimizationLevel = -1
|
||||
context.languageVersion = Context.VERSION_ES6
|
||||
context.locale = Locale.getDefault()
|
||||
context.wrapFactory = WrapFactory()
|
||||
}
|
||||
|
||||
protected fun removeContext(context: Context) {
|
||||
context.wrapFactory = null
|
||||
// FIXME 同时跑多个UI脚本时会共用同一个Context 此时的WrapFactory会被覆盖
|
||||
if (context.isSealed) {
|
||||
unsealed = true
|
||||
context.unseal(sContextEngineMap[context]?.mScriptable)
|
||||
} else {
|
||||
context.optimizationLevel = -1
|
||||
context.languageVersion = Context.VERSION_ES6
|
||||
context.locale = Locale.getDefault()
|
||||
context.wrapFactory = WrapFactory()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class WrapFactory : org.mozilla.javascript.WrapFactory() {
|
||||
|
||||
@ -25,6 +25,8 @@ import com.stardust.autojs.script.ScriptSource;
|
||||
|
||||
import org.mozilla.javascript.ContinuationPending;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/2/5.
|
||||
*/
|
||||
@ -39,7 +41,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
||||
private ScriptExecutionListener mExecutionListener;
|
||||
private ScriptSource mScriptSource;
|
||||
private ActivityScriptExecution mScriptExecution;
|
||||
private ScriptRuntime mRuntime;
|
||||
private WeakReference<ScriptRuntime> mRuntime;
|
||||
|
||||
|
||||
private EventEmitter mEventEmitter;
|
||||
@ -72,8 +74,8 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
||||
mScriptSource = mScriptExecution.getSource();
|
||||
mScriptEngine = mScriptExecution.createEngine(this);
|
||||
mExecutionListener = mScriptExecution.getListener();
|
||||
mRuntime = ((JavaScriptEngine) mScriptEngine).getRuntime();
|
||||
mEventEmitter = new EventEmitter(mRuntime.bridges);
|
||||
mRuntime = new WeakReference<> (((JavaScriptEngine) mScriptEngine).getRuntime());
|
||||
mEventEmitter = new EventEmitter(mRuntime.get().bridges);
|
||||
runScript();
|
||||
emit("create", savedInstanceState);
|
||||
}
|
||||
@ -228,7 +230,7 @@ public class ScriptExecuteActivity extends AppCompatActivity {
|
||||
try {
|
||||
mEventEmitter.emit(event, (Object[]) args);
|
||||
} catch (Exception e) {
|
||||
mRuntime.exit(e);
|
||||
mRuntime.get().exit(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.stardust.autojs.rhino;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.mozilla.javascript.EmbeddedSlotMap;
|
||||
import org.mozilla.javascript.IdScriptableObject;
|
||||
import org.mozilla.javascript.ImporterTopLevel;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
@ -9,38 +8,69 @@ import org.mozilla.javascript.TopLevel;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TopLevelScope extends ImporterTopLevel {
|
||||
private static final Logger logger = Logger.getLogger(TopLevelScope.class);
|
||||
private static final ScheduledExecutorService scheduledExecutor = new ScheduledThreadPoolExecutor(1, r -> {
|
||||
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||
t.setName("TopLevelScope recycle");
|
||||
return t;
|
||||
});
|
||||
private final long createdStamp;
|
||||
private long releasedStamp;
|
||||
private long recycledStamp;
|
||||
private String engineSource;
|
||||
private boolean noRecycle;
|
||||
|
||||
public TopLevelScope() {
|
||||
super();
|
||||
this.createdStamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/* 不需要覆写 直接交给回收线程1分钟后自动回收
|
||||
@Override
|
||||
public void finalize() throws Throwable {
|
||||
logger.debug("回收TopLevelScope资源[" + engineSource + "], 存活总时间:"
|
||||
+ (System.currentTimeMillis() - this.createdStamp)
|
||||
+ "ms 释放后经过:" + (System.currentTimeMillis() - releasedStamp) + "ms");
|
||||
// recycle();
|
||||
+ "ms 释放后经过:" + (System.currentTimeMillis() - releasedStamp) + "ms"
|
||||
+ (recycledStamp > 0 ? " 资源已经在[" + recycledStamp + "]被完全释放" : "")
|
||||
);
|
||||
super.finalize();
|
||||
recycle();
|
||||
}
|
||||
*/
|
||||
|
||||
public void markReleased(String engineSource) {
|
||||
this.engineSource = engineSource;
|
||||
releasedStamp = System.currentTimeMillis();
|
||||
logger.debug("标记TopLevelScope资源已不再使用[" + engineSource + "], 存活总时间:"
|
||||
+ (releasedStamp - this.createdStamp) + "ms");
|
||||
if (noRecycle) {
|
||||
return;
|
||||
}
|
||||
// 1分钟后强制回收
|
||||
scheduledExecutor.schedule(this::recycle, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收资源,验证下来发现不是很必要 暂时不处理
|
||||
*/
|
||||
private void recycle() {
|
||||
if (recycledStamp > 0) {
|
||||
return;
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
Field importedPackagesField = ImporterTopLevel.class.getDeclaredField("importedPackages");
|
||||
importedPackagesField.setAccessible(true);
|
||||
importedPackagesField.set(this, null);
|
||||
} catch (Exception e) {
|
||||
// ...
|
||||
}
|
||||
try {
|
||||
Field associatedValuesField = ScriptableObject.class.getDeclaredField("associatedValues");
|
||||
associatedValuesField.setAccessible(true);
|
||||
@ -64,7 +94,7 @@ public class TopLevelScope extends ImporterTopLevel {
|
||||
try {
|
||||
Field slotMap = ScriptableObject.class.getDeclaredField("slotMap");
|
||||
slotMap.setAccessible(true);
|
||||
slotMap.set(this, new EmbeddedSlotMap());
|
||||
slotMap.set(this, null);
|
||||
} catch (Exception e) {
|
||||
//
|
||||
}
|
||||
@ -91,5 +121,18 @@ public class TopLevelScope extends ImporterTopLevel {
|
||||
// ...
|
||||
}
|
||||
setPrototype(null);
|
||||
logger.debug("彻底回收[" + engineSource + "]资源 释放后经过:"
|
||||
+ (System.currentTimeMillis() - releasedStamp)
|
||||
+ "ms 耗时:" + (System.currentTimeMillis() - start) + "ms");
|
||||
recycledStamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记当前脚本可以持续运行 不被主动回收 否则十分钟后必备回收
|
||||
* 适用于new java.lang.Thread 之类的 后台线程 但实际上不应该有这种操作 很容易内存泄露
|
||||
* 这里保留这么个入口
|
||||
*/
|
||||
public void setNoRecycle() {
|
||||
noRecycle = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,6 +250,14 @@ public class ScriptRuntime {
|
||||
mTopLevelScope = new WeakReference<>(topLevelScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使脚本可以在脚本引擎结束后 持续运行 不被回收
|
||||
* 最好不要使用,如果不能准时回收 可能导致内存泄露问题
|
||||
*/
|
||||
public void setNoRecycle() {
|
||||
mTopLevelScope.get().setNoRecycle();
|
||||
}
|
||||
|
||||
public static void setApplicationContext(Context context) {
|
||||
applicationContext = new WeakReference<>(context);
|
||||
}
|
||||
@ -384,6 +392,9 @@ public class ScriptRuntime {
|
||||
}
|
||||
|
||||
public void exit(Throwable e) {
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
engines.myEngine().uncaughtException(e);
|
||||
exit();
|
||||
}
|
||||
|
||||
@ -131,8 +131,10 @@ public class VMBridge_custom extends VMBridge_jdk18 {
|
||||
|
||||
private Object call(ContextFactory cf, ContextAction action) {
|
||||
Context cx = Context.enter(null, cf);
|
||||
//TODO null check
|
||||
cx.setWrapFactory(mCallerContext.getWrapFactory());
|
||||
if (!cx.isSealed()) {
|
||||
// TODO null check
|
||||
cx.setWrapFactory(mCallerContext.getWrapFactory());
|
||||
}
|
||||
try {
|
||||
return action.run(cx);
|
||||
} finally {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user