其中cmake项是配置CMakeLists的路径和CMake的版本。 默认情况下,Gradle(无论是通过 Android Studio 使用,还是从命令行使用)会针对所有非弃用 ABI 进行构建。要限制应用支持的 ABI 集,请使用 abiFilters。如这里我填入了arm64-v8a,正是对应了编译FFmpeg时指定的目标CPU和架构。如果这里不设置abiFilter的话,之后构建app会出现问题。
The error you’re getting means that ndk-build is trying to use your .so file while compiling your module for an incompatible target. Android supports several cpu architectures (armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64). You can choose which one you want to support using the APP_ABI variable from Application.mk. If you set it to all, ndk-build will try to use this .so file you’re referencing for each of these architectures, but this cannot work. Your .so file must have been compiled for Android platforms, and you need to have a different version of it for each architecture you’re supporting. You can give a dynamic reference to the right .so file, such as LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libslabhidtouart.so so it looks for your .so file under armeabi-v7a folder when compiling for armeabi-v7a, under x86 for x86, etc. Of course you need to provide these .so files. If you can’t get .so files for all the supported architectures, you’ll have to restrict your APP_ABI to the architectures of the .so file you have. You can determine the architecture your .so file has been compiled for using readelf.
if (!sws) { av_log(nullptr, AV_LOG_INFO, "Cannot create sws context.\n"); }
// open writing stream of output file outputFile = fopen(outputPath, "wb"); if (!outputFile) { av_log(nullptr, AV_LOG_ERROR, "Failed to open output file!\n"); }
...
if (!x264Param) delete x264Param; x264Param = newx264_param_t; int ret = x264_param_default_preset(x264Param, "fast", "zerolatency"); if (ret < 0) { av_log(nullptr, AV_LOG_ERROR, "Failed to set preset parameter!\n"); }
...
ret = x264_param_apply_profile(x264Param, x264_profile_names[1]); if (ret < 0) { av_log(nullptr, AV_LOG_ERROR, "Failed to apply main profile!\n"); }
encoder = x264_encoder_open(x264Param); if(!encoder) { av_log(nullptr, AV_LOG_ERROR, "Failed to open x264 encoder!\n"); }
// Write headers to file int header_size = x264_encoder_headers(encoder, &nals, &nalCount); if(header_size < 0) { av_log(nullptr, AV_LOG_ERROR, "Error when calling x264_encoder_headers()!\n"); } // outputStream << nals[0].p_payload; if (!fwrite(nals[0].p_payload, sizeof(uint8_t), header_size, outputFile)) { av_log(nullptr, AV_LOG_ERROR, "Failed to write header!\n"); }
必要的内存释放代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
X264Encoder::~X264Encoder() { if (encoder) { x264_picture_clean(&inFrame); x264_encoder_close(encoder); encoder = nullptr; } if (outputFile) { fclose(outputFile); outputFile = nullptr; } if (sws) { sws_freeContext(sws); sws = nullptr; } delete mbQp; }
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over. See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks ==================================== 0 UNREACHABLE OBJECTS
An unreachable object is still in memory but LeakCanary could not find a strong reference path from GC roots. ==================================== METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 30 Build.MANUFACTURER: unknown LeakCanary version: 2.9.1 App process name: com.eynnzerr.avplayer Class count: 19677 Instance count: 104943 Primitive array count: 86281 Object array count: 17593 Thread count: 20 Heap total bytes: 16092598 Bitmap count: 0 Bitmap total bytes: 0 Large bitmap count: 0 Large bitmap total bytes: 0 Stats: LruCache[maxSize=3000,hits=35774,misses=78728,hitRate=31%] RandomAccess[bytes=3855083,reads=78728,travel=23243159115,range=18698859,size=24675970] Heap dump reason: user request Analysis duration: 3073 ms Heap dump file path: /storage/emulated/0/Download/leakcanary-com.eynnzerr.avplayer/2022-10-27_22-39-44_274.hprof Heap dump timestamp: 1666881589949 Heap dump duration: 1291 ms
// 如果已经安装过了则抛异常 if (isInstalled) { throw IllegalStateException( "AppWatcher already installed, see exception cause for prior install call", installCause ) }
// 检查输入参数 retainedDelayMillis 的合法性 check(retainedDelayMillis >= 0) { "retainedDelayMillis $retainedDelayMillis must be at least 0 ms" } this.retainedDelayMillis = retainedDelayMillis
// debug时开启日志 if (application.isDebuggableBuild) { LogcatSharkLog.install() }
// Requires AppWatcher.objectWatcher to be set LeakCanaryDelegate.loadLeakCanary(application)
// 为传入待注册的 InstallableWatcher 一一调用注册 watchersToInstall.forEach { it.install() } // Only install after we're fully done with init. installCause = RuntimeException("manualInstall() first called here") }
val durationMillis: Long if (currentEventUniqueId == null) { currentEventUniqueId = UUID.randomUUID().toString() } try { InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
if (heapDumpFile == null) { throw RuntimeException("Could not create heap dump file") } saveResourceIdNamesToMemory() val heapDumpUptimeMillis = SystemClock.uptimeMillis() KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis durationMillis = measureDurationMillis { // 关键:执行dump hprof到指定文件 configProvider().heapDumper.dumpHeap(heapDumpFile)
staticclassEntryextendsWeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
注:Byte, Short, Int, Long 还具有对应无符号类型:UByte, UShort, UInt, ULong。
字面量写法:
1 2 3 4 5 6 7
val intNum = 100// 默认推断为 Int val longNum = 100L// L 后缀表示 Long val floatNum = 3.14f// f 后缀表示 Float val doubleNum = 3.14// 默认推断为 Double val hexNum = 0xFF// 十六进制 val binaryNum = 0b1010// 二进制 val readable = 1_000_000// 支持下划线分隔,提升可读性
显式类型转换: ⚠️Kotlin 不支持隐式类型转换,必须显式调用转换函数:
1 2 3
val i: Int = 100 val l: Long = i.toLong() // ✅ 正确 // val l: Long = i // ❌ 编译错误
1.1.1 Number 类
以上数值类型都有一个共同的父类:Number,这是一个抽象类,并定义了一系列显示类型转换函数:
1 2 3 4 5 6 7 8 9
publicabstractclassNumber { publicabstractfuntoDouble(): Double publicabstractfuntoFloat(): Float publicabstractfuntoLong(): Long publicabstractfuntoInt(): Int publicabstractfuntoChar(): Char// Deprecated publicabstractfuntoShort(): Short publicabstractfuntoByte(): Byte }
通过继承 Number类并实现 Comparable接口,派生出以上数值类型。
1.2 字符与字符串
Char 表示单个字符(16-bit Unicode),用单引号包裹;字符串用双引号或三引号表示:
1 2 3 4 5 6
val letter: Char = 'A' val str = "Hello, Kotlin" val multiLine = """ 这是多行字符串 保留原始格式 """.trimIndent()
val char: Char = 'A' val i: Int = char.code // Code of a Char is the value it was constructed with, and the UTF-16 code unit corresponding to this Char. val l: Long = char.code.toLong() val c: Char = i.toChar() // significant 16 bits of this Int value. val d: Char = i.digitToChar() // decimal digit
Kotlin 支持字符串模板,这在构造特定字符串以及日志打印等场景都非常好用:
1 2 3
val name = "World" println("Hello, $name!") // 简单变量 println("长度是 ${name.length}") // 表达式
Kotlin String 还有更多值得说道的知识点,在此先略过,之后另写一篇博客来详细探讨。
1.3 布尔类型与数组
1 2 3 4 5 6 7
val isKotlinFun: Boolean = true
// 泛型数组 val arr = arrayOf(1, 2, 3)
// 原始类型数组(避免装箱开销) val intArr = intArrayOf(1, 2, 3)
二、数值字面量的实现机制
既然 Kotlin 中 Int 是一个类,为什么 val x = 1 不需要写成 val x = Int(1) 这样的构造函数形式?
2.1 字面量是语言级别的语法糖
数值字面量是 Kotlin 语言规范直接支持的特殊语法,编译器看到 1 时,直接将其识别为 Int 类型的字面量常量,不需要经过任何构造函数调用。
1 2
val x = 1// 字面量语法,编译器直接处理 val y = Int(1) // ❌ 编译错误!Int 没有公开构造函数
2.2 为什么 Int 没有构造函数?
Kotlin 的数值类型是特殊的内置类型,构造函数是私有的:
设计原因
说明
性能优化
编译器可以直接映射到 JVM 原始类型,无需真正的对象分配
语义清晰
1 就是 1,不需要 new Integer(1) 的冗余
防止滥用
避免 Int(someString) 这种容易出错的用法
2.3 编译过程示例
1 2 3 4 5 6 7 8
┌─────────────────────────────────────────────────────┐ │ Kotlin 源码 val x = 1 │ ├─────────────────────────────────────────────────────┤ │ 编译器理解 x 是 Int 类型,值为字面量 1 │ ├─────────────────────────────────────────────────────┤ │ 字节码输出 ICONST_1 (JVM 原始 int 指令) │ │ ISTORE x │ └─────────────────────────────────────────────────────┘
Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance. All direct subclasses of a sealed class are known at compile time. No other subclasses may appear outside a module within which the sealed class is defined. For example, third-party clients can’t extend your sealed class in their code. Thus, each instance of a sealed class has a type from a limited set that is known when this class is compiled.
The same works for sealed interfaces and their implementations: once a module with a sealed interface is compiled, no new implementations can appear.
In some sense, sealed classes are similar to enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances, each with its own state.
从以上介绍可以大致总结出 Sealed Class/Interface 有如下特性:
表示受限的类层次结构,并可以对继承提供更多的控制
密封类的子类在编译期即被确定为一个有限的集合内,不可扩展
密封类的子类只能位于定义了该密封类的包中
密封类的子类可以有多个实例
介绍中还提到,sealed class 和 enum class 在某种意义上是类似的,实际上,sealed class 诞生的重要原因之一正是为了克服 enum class 在某些场合下的局限性,也即上述特性的第一点和第四点。我们知道,枚举类有两个特性,在某些场合下是优点,但在另外一些场合下却可能成为缺点: