From 58ba1ff71aca7ca4d88c3471097a7e4da412df0f Mon Sep 17 00:00:00 2001 From: TonyJiangWJ Date: Sat, 8 Jan 2022 02:15:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96ScriptRuntime=E5=92=8CTopLeve?= =?UTF-8?q?lScope=E7=9A=84=E8=B5=84=E6=BA=90=E5=9B=9E=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/inflater/DynamicLayoutInflater.java | 9 ++++ .../autojs/engine/RhinoJavaScriptEngine.kt | 24 ++++++--- .../execution/ScriptExecuteActivity.java | 10 ++-- .../stardust/autojs/rhino/TopLevelScope.java | 51 +++++++++++++++++-- .../autojs/runtime/ScriptRuntime.java | 11 ++++ .../mozilla/javascript/VMBridge_custom.java | 6 ++- 6 files changed, 93 insertions(+), 18 deletions(-) diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java index 61bb97d8..77680582 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/inflater/DynamicLayoutInflater.java @@ -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> createdViews = new LinkedList<>(); private Context mContext; private ResourceParser mResourceParser; + private WeakReference 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); } } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt index e81d9a41..f414d48c 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt +++ b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.kt @@ -36,6 +36,7 @@ open class RhinoJavaScriptEngine(private val mAndroidContext: android.content.Co private val context: WeakReference private val mScriptable: WeakReference + private var unsealed: Boolean = false lateinit var threadRef: WeakReference 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 { 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() { diff --git a/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java b/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java index b671b9ee..f658dd8f 100644 --- a/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java +++ b/autojs/src/main/java/com/stardust/autojs/execution/ScriptExecuteActivity.java @@ -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 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); } } diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/TopLevelScope.java b/autojs/src/main/java/com/stardust/autojs/rhino/TopLevelScope.java index 2a61f929..33e606ae 100644 --- a/autojs/src/main/java/com/stardust/autojs/rhino/TopLevelScope.java +++ b/autojs/src/main/java/com/stardust/autojs/rhino/TopLevelScope.java @@ -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; } } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java index fd8c4518..79bf79ff 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -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(); } diff --git a/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java b/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java index 079c2310..ffdeebfc 100644 --- a/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java +++ b/autojs/src/main/java/org/mozilla/javascript/VMBridge_custom.java @@ -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 {