AutoJs6占用7GB内存
手机:Redmi Note 11T Pro,安卓13 MIUI 14.0.7
已知AutoJs6 版本:6.6.2、6.6.1
我的脚本中使用多个线程,高频使用images.findImage()函数,其它地方的image对象不使用了后都及时使用了recycle()回收。但随着脚本运行RAM占用越来越大,高达7GB,然后使其它软件崩溃。即使结束了脚本,AutoJs6仍然占7GB内存,“最近任务”和手机管家都不能清除AutoJs6,除非在应用详情页手动停止本软件运行才能释放内存。 怀疑是images.findImage()函数有问题。
这个情况经常发生。
看下AI的分析:https://deepwiki.com/search/imagesfindimageautojs6ramrambu_475c2f12-0d86-4649-82f9-f7c5caa9657f
没人??
??
不建议 Auto.js 使用大量线程并行执行操作, 你可以尝试在代码层面达到同等目标的同时, 尽量减低线程数量的使用.
AutoJs6 提供了 ImageWrapper#oneShot 方法, 可用于方法结束后自动回收图像资源. 例如:
let max = 10e3;
while (max--) {
console.log(images.findImage(images.read('imgA.png').oneShot(), images.read('imgB.png').oneShot()));
}
上述示例代码进行了一万次图像查找 (仅作示例), 本地读取的图像 (imgA 与 imgB) 均进行了 oneShot() 标记, 这样在每次 findImage 方法结束之后, 都会自动回收图像资源.
注: images.captureScreen() 得到的图像通常不需要显示回收.
如仍有问题, 可提供实际使用的关键代码, 以便于进一步分析.
非常感谢大佬作者一直以来的更新 @SuperMonster003 在我看来 AutoJS6比AutoJSPro都好用。 尤其是在开发体验上,AutoJS6的插件更加的方便 相信作者是非常注重这个的项目的才会在体验上如此下功夫 但是目前Autojs最严重最致命的问题 就是findImage内存泄露的问题。 而且非常容易复现 即使我每次都回收了图片 但是他依然存在泄露问题 我仔细阅读了autojs findImage的源码 但是对于Android 我不太熟悉 我也在本地调试应用并使用LeakCanary尝试发现点什么 然后什么都没有发现 但是内存确实是在一次次的findImage调用后上涨。 经过验证 在 6.5版本并没有泄露问题 6.6.0版本开始就有内存泄露问题了。复现的代码如下。 如果可能的话 烦请大佬帮忙解答下大概是哪里出问题了。
let runtime = java.lang.Runtime.getRuntime();
log('初始的状态: ' + getMemoryData());
function getMemoryData() {
let maxMemory = runtime.maxMemory(); // 能从操作系统那里挖到的最大的内存 单位字节Byte 536870912B === 512MB
let totalMemory = runtime.totalMemory(); // 已经从操作系统那里挖过来的内存大小, 慢慢挖, 用多少挖多少
let freeMemory = runtime.freeMemory(); // 挖过来而又没有用上的内存, 一般情况下都是很小
let arr = [];
arr.push('最大内存 = ' + (maxMemory / 1024 / 1024).toFixed(2) + 'MB');
arr.push('已用内存 = ' + (totalMemory / 1024 / 1024).toFixed(2) + 'MB');
arr.push('空闲内存 = ' + (freeMemory / 1024 / 1024).toFixed(2) + 'MB');
return arr.join(', ');
}
let max = 10e3;
let index = 0;
let img1;
let img2;
while (index < max) {
img1 = images.read('/sdcard/Pictures/test.png');
img2 = images.read('/sdcard/Pictures/test1.png');
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
images.findImage(img1, img2);
img1.recycle();
img2.recycle();
if (index % 10 === 0) {
runtime.gc();
log(getMemoryData());
}
index++;
sleep(200);
}
经对比测试 Auto.js 4.1.1 Alpha2 与 AutoJs6 6.6.4, 确实存在 AutoJs6 运行上述代码后出现内存泄漏的问题 (Auto.js 4.x 正常). 我会仔细核查 AutoJs6 6.6.0 与 6.5.0 以及 Auto.js 4.x 之间的代码差异, 并在近期给出异常出现的原因及可能的解决方案.
Sent from my XQ-DQ72 using FastHub
@SuperMonster003 大佬不用看了 我这段代码的问题找到了。。。 原来是 log 的问题
// log(getMemoryData());
const memoryText = getMemoryData();
log(memoryText);
改成这样内存就不增长了... 按道理来说 getMemoryData 返回的是个字符串 不应该啊。..
@SuperMonster003 大佬忽略我上个回答。 Rhino 引擎可能和js引擎有点不一样。 在循环里面使用 const来定义数据会将数据缓存起来。。 所以希望看不到内存增长了。 其实内存还是在增长。
6.6.0 版本开始, 参考 Auto.js Pro 引入了 MonitorResource 接口, Cleaner 类等, 试图实现自动回收机制. 但直到 6.6.4 也没有完全实现, 反而因 ConcurrentHashMap 持有强引用对象导致 GC 无法正常回收 ImageWrapper 对象引起内存泄露.
去除与 MonitorResource 及 Cleaner 等相关的代码后, 可解决上述内存泄漏问题, 参考代码如下 (org.autojs.autojs.core.image.ImageWrapper.kt):
open class ImageWrapper : Shootable<ImageWrapper> {
private var mScriptRuntime: ScriptRuntime
private var mMat: Mat? = null
private var mBgrMat: Mat? = null
private var mBitmap: Bitmap? = null
private var mMediaImage: Image? = null
private var mPlane: Image.Plane? = null
private var mWidth = 0
private var mHeight = 0
private var mIsRecycled = false
private var mIsOneShot = false
val width
get() = mWidth.also { ensureNotRecycled() }
val height
get() = mHeight.also { ensureNotRecycled() }
val size
get() = Size(mWidth.toDouble(), mHeight.toDouble()).also { ensureNotRecycled() }
val bitmap: Bitmap
get() {
ensureNotRecycled()
if (mBitmap == null) {
val mat = mMat
if (mat != null) {
val bitmap = createBitmap(mat.width(), mat.height()).also { mBitmap = it }
Utils.matToBitmap(mat, bitmap)
} else {
mMediaImage?.let { mBitmap = toBitmap(it) }
}
}
return mBitmap ?: throw Exception("Bitmap of ImageWrapper should never be null")
}
val mat: Mat
get() {
ensureNotRecycled()
val mat = mMat
if (mat != null) {
return mat
}
val bitmap = mBitmap
if (bitmap != null) {
val newMat = Mat().also { mMat = it }
Utils.bitmapToMat(bitmap, newMat)
return newMat
}
if (mMediaImage != null) {
val plane = plane ?: throw AssertionError("Image plain is null")
plane.buffer.position(0)
return Mat(mHeight, mWidth, CvType.CV_8UC4, plane.buffer, plane.rowStride.toLong()).also { mMat = it }
}
throw AssertionError("Both bitmap and image are null")
}
val bgrMat
get() = Mat().also {
Imgproc.cvtColor(mat, it, Imgproc.COLOR_BGRA2BGR)
mBgrMat = it
}
var plane: Image.Plane?
private set(plane) {
mPlane = plane
}
get() = mPlane ?: mMediaImage?.planes?.get(0)
constructor(scriptRuntime: ScriptRuntime, width: Int, height: Int) : this(scriptRuntime, createBitmap(width, height))
constructor(scriptRuntime: ScriptRuntime, bitmap: Bitmap) {
mScriptRuntime = scriptRuntime
mBitmap = bitmap.also { addToList(it) }
mWidth = bitmap.width
mHeight = bitmap.height
}
constructor(scriptRuntime: ScriptRuntime, mat: Mat) {
mScriptRuntime = scriptRuntime
mMat = mat.also { addToList(it) }
mWidth = mat.cols()
mHeight = mat.rows()
}
constructor(scriptRuntime: ScriptRuntime, mat: org.opencv.core.Mat) {
mScriptRuntime = scriptRuntime
mMat = when (mat.nativeObj != 0L) {
true -> Mat(mat.nativeObj)
else -> Mat(mat.rows(), mat.cols(), mat.type())
}.also { addToList(it) }
mWidth = mat.cols()
mHeight = mat.rows()
}
constructor(scriptRuntime: ScriptRuntime, bitmap: Bitmap, mat: Mat?) {
mScriptRuntime = scriptRuntime
mMat = mat?.also { addToList(it) }
mBitmap = bitmap.also { addToList(it) }
mWidth = bitmap.width
mHeight = bitmap.height
}
constructor(scriptRuntime: ScriptRuntime, mediaImage: Image) {
mScriptRuntime = scriptRuntime
mMediaImage = mediaImage.also { addToList(it) }
mWidth = mediaImage.width
mHeight = mediaImage.height
}
init {
Images.initOpenCvIfNeeded()
}
private fun addToList(image: Any) {
imageList.add(WeakReference(image))
}
fun saveTo(path: String): Boolean {
ensureNotRecycled()
val fullPath = mScriptRuntime.files.nonNullPath(path)
if (mBitmap == null) {
if (mMat != null) {
return Imgcodecs.imwrite(fullPath, mMat)
}
bitmap /* Getter, for initializing `mBitmap`. */
}
return runCatching { saveWithBitmap(fullPath) }.isSuccess
}
private fun saveWithBitmap(fullPath: String?) {
try {
fullPath ?: throw Exception("Argument \"path\" cannot be null")
mBitmap ?: throw Exception("Member \"bitmap\" cannot be null")
mBitmap!!.compress(CompressFormat.PNG, 100, FileOutputStream(fullPath))
} catch (e: FileNotFoundException) {
throw UncheckedIOException(e)
}
}
fun pixel(x: Int, y: Int): Int {
ensureNotRecycled()
if (x < 0 || y < 0 || x >= width || y >= height) {
throw ArrayIndexOutOfBoundsException("Point ($x, $y) out of bounds of $this")
}
mBitmap?.let { oBitmap ->
return oBitmap[x, y]
}
mMat?.let { oMat ->
// @Caution by SuperMonster003 on Oct 30, 2024.
// ! Not that we should pass (y, x) instead of (x, y) here.
// ! Pay attention to the params of `org.opencv.core.Mat.get(row, col)` method,
// ! where "row" corresponds "y", and "col" corresponds "x".
// ! zh-CN:
// ! 这里需要注意不能传入 (x, y), 而需要传入 (y, x).
// ! 注意 `org.opencv.core.Mat.get(row, col)` 方法参数, "row" 对应 "y", "col" 对应 "x".
val pixelList = oMat.get(/* row = */ y, /* col = */ x) ?: throw Exception("Channel list is null at ($x, $y) of $this")
// @Hint by SuperMonster003 on Apr 9, 2025.
// ! Here we assume that pixelList contains ARGB channels.
// ! If mMat does not have these channels, an IndexOutOfBoundsException will be triggered:
// # java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
// ! Therefore, it is necessary to consider cases with different numbers of channels.
// !
// ! Refer to: http://issues.autojs6.com/350
// !
// ! zh-CN:
// ! 此处默认 pixelList 为 ARGB 通道, 当 mMat 不是上述通道时, 将触发索引越界异常:
// # java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
// ! 因此需要考虑不同通道数量的情况.
// !
// ! 参阅: http://issues.autojs6.com/350
// !
// # val (r, g, b, a) = pixelList.map { pixelValue -> pixelValue.toInt() }
// # return Color.argb(a, r, g, b)
return when (pixelList.size) {
1 -> {
// Single-channel image (grayscale/binary/...)
// zh-CN: 单通道图像 (灰度图/二值图/...)
val value = pixelList[0].toInt()
Color.argb(255, value, value, value)
}
3 -> {
// RGB
val (r, g, b) = pixelList.map { it.toInt() }
Color.argb(255, r, g, b)
}
in 4..Int.MAX_VALUE -> {
// RGBA
val (r, g, b, a) = pixelList.map { it.toInt() }
Color.argb(a, r, g, b)
}
else -> {
throw Exception("Unsupported pixel channel count (${pixelList.size}) at ($x, $y) of $this")
}
}
}
plane?.let { oPlane ->
val buffer = oPlane.buffer.apply { position(0) }
return abgrToArgb(buffer.getInt(oPlane.pixelStride * x + oPlane.rowStride * y))
}
throw Exception("At least one of bitmap, mat mad plane must be non-null")
}
override fun recycle() {
synchronized(this) {
mBitmap?.let {
it.recycle()
mBitmap = null
}
mMat?.let {
OpenCVHelper.release(it)
mMat = null
}
mBgrMat?.let {
OpenCVHelper.release(it)
mBgrMat = null
}
mMediaImage?.let {
it.close()
mMediaImage = null
mPlane = null
}
mIsRecycled = true
}
}
override fun setOneShot(b: Boolean) = also { mIsOneShot = b }
override fun shoot() {
if (mIsOneShot) recycle()
}
override fun isRecycled() = mIsRecycled
@ScriptInterface
fun ensureNotRecycled() {
check(!isRecycled) { str(R.string.error_image_has_been_recycled) }
}
fun clone(): ImageWrapper {
ensureNotRecycled()
return when (val bitmap = mBitmap) {
null -> ofMat(mScriptRuntime, mat.clone())
else -> ofBitmap(mScriptRuntime, bitmap.copy(bitmap.config ?: Bitmap.Config.ARGB_8888, true))
}
}
/* 伴生对象代码省略. */
/* companion object { ... ... } */
}
你可以尝试下载并试用项目程序包:
- autojs6-v6.7.0-alpha2-universal-382bf512.apk (OneDrive)
- autojs6-v6.7.0-alpha2-universal-382bf512.zip (城通网盘, pw: 5334, 需解压)
如问题复现, 可继续反馈.
@SuperMonster003 经过验证 内存上涨大福减少 10个小时大概上涨20M. 我有空在测试下Autojs6.5的版本10个小时上涨多少。 感谢大佬。 能分享下如何修改的吗 我尝试在Autojs6.6.4清理你所说的相关代码 跑不起来了。
尝试下载 6_6_4_-Patch-修复_images_部分相关方法可能引发内存泄露的问题(issue_#372).patch 到本地
IDE 菜单 (以 IntelliJ IDEA 2025.1.3 为例) Git | Patch | Apply patch... 选择 *.patch 文件所在路径 应用补丁到 AutoJs6 6.6.4 项目源码
你可以尝试下载并试用项目程序包:
- autojs6-v6.7.0-alpha2-universal-382bf512.apk (OneDrive)
- autojs6-v6.7.0-alpha2-universal-382bf512.zip (城通网盘, pw: 5334, 需解压)
如问题复现, 可继续反馈.
我试用了autojs6-v6.7.0-alpha2,内存占用还是会很大。我没头绪了。
@xjqm-tao
若仍有高内存占用情况, 也可能并非 images 模块相关方法本身的问题, 参阅 #403.