症状
使用Android手机向某些电话号码拨打电话时,由于运营商自动播放视频彩铃,导致电话自动转成视频通话,但由于某些bug,导致电话自动挂断,因而无法打出电话的问题
即:拨打电话,响铃几秒后,提示正在进行运营商视频通话,然后提示通话结束。对方能够听到短时间的响铃。
使用Android手机向某些电话号码拨打电话时,由于运营商自动播放视频彩铃,导致电话自动转成视频通话,但由于某些bug,导致电话自动挂断,因而无法打出电话的问题
即:拨打电话,响铃几秒后,提示正在进行运营商视频通话,然后提示通话结束。对方能够听到短时间的响铃。
通常情况下单例模式的对象不应该具有状态,然而现实是复杂的,总会有那么一些特殊情况下需要小小地【违例】一下。
一个父类的方法执行前需要设置一个变量的值,变量值会对方法的执行结果产生影响。现希望子类以单例的方式继承父类。
以我实际遇到的一个问题为例,JOOQ 是一个 ORM 类库,这个类库能够自动扫描数据库并生成 DAO,但是自动生成的 DAO 功能有限,通常需要继承来扩展这些 DAO。每个 DAO 在实例化时需要传入一个 Configuration
,这个 Configuration
包含有关数据库的信息。
通常情况下 Configuration
可以由所有的 DAO 共享,然而,在启动事务后 Configuration
会被 JOOQ 派生,且后续所有的事务内数据库操作都应该使用派生的 Configuration
。
现希望子 DAO 为单例,同时,在不改变父 DAO 的情况下,且不修改子 DAO 的已有方法和对这些方法的调用的情况下,实现 configuration 变量的 “智能” 修改。
我们可以利用 ThreadLocal + Lambda 解决这个问题。
ThreadLocal<E>
是一个容器,它内部采用 Map 实现,以线程的某种唯一特征为键,用户自定义类型 E
为值。因此不同线程存取同一个 ThreadLocal 容器会得到不同的值,且不同线程互不影响。一个线程总是只能访问到属于他自己的那份值。
建议先跳过本节
Kotlin 协程除了具有协程的本身本性之外,实际上是由 Kotlin 管理的一个线程池。
协程的挂起指的是当前正在运行一块协程代码的线程从这块代码脱离,不再负责这块代码的执行。线程暂时处于空闲状态。每当执行到一个 suspend 函数调用时,都会发生挂起。
协程挂起发生后,开始执行 suspend 函数,负责具体执行这个函数的线程由函数的 withContext()
调用决定。注意:此时可能发生了线程的切换,脱离了原先的线程。
suspend 函数完成后,刚才挂起的协程代码恢复执行。注意:此时可能再次发生线程切换,刚才没执行的代码继续回到原先的线程执行。
要解决一开始提出的问题,容易想到,我们可以利用 ThreadLocal 声明一个“全局”变量,当 DAO 需要用到 Configuration
时,就从 ThreadLocal 容器去取。
同时,再声明一个函数,负责临时更改 ThreadLocal
容器的 Configuration
具体值,当传入的 lambda 执行完毕后再改回原先状态。
传入的 lambda,就是我们希望在新的 Configuration
上下文中所执行的代码。
fun <R> Configuration.use(then: ((Configuration) -> R)): R {
//jooqConfigurationOrNull 是负责管理存储 Configuration 的属性
val initialState = jooqConfigurationOrNull //保存初始状态
jooqConfigurationOrNull = this //临时变更为新状态
return try { //注意捕获异常,防止发生异常时无法还原状态
then(this) //执行传入的 lambda 代码块
} catch (exception: Throwable) {
logger.debug("Config use block failed: $exception")
throw exception //原样抛出异常
} finally {
logger.debug("Recover thread local jooq config to initial")
jooqConfigurationOrNull = initialState //还原初始状态
}
}
var currentThreadJooqConfiguration: Configuration
get() = currentThreadJooqConfigurationOrNull ?: jooqConfiguration
set(value) { currentThreadJooqConfigurationOrNull = value }
//合理利用 getter 和 setter 让 ThreadLocal 对用户不可见
var jooqConfigurationOrNull: Configuration?
get() = currentThreadJooqConfigurationContainer.get()
set(value) {
if (value == null)
currentThreadJooqConfigurationContainer.remove()
else
currentThreadJooqConfigurationContainer.set(value)
}
// 真正的全局 ThreadLocal 容器
private val currentThreadJooqConfigurationContainer = ThreadLocal<Configuration>()
val jooqConfiguration: Configuration
get() = realJooqConfiguration.derive() // derive() 等效 clone(),直接取的总是派生出来的。
private lateinit var realJooqConfiguration: Configuration // 派生的根基。只做派生用途。
由于使用了 ThreadLocal
,不同线程从该容器取出的结果各不相同且不会互相影响。同时,由于我们的代码霸占着这个线程,因此这里虽然临时改变了 jooqConfigurationOrNull
,但是对其他线程并没有影响。
针对一开始提出的问题,要让父 DAO 获得的 Configuration
也发生改变,只需重写父类的 getter,让父类总是从 ThreadLocal 容器获得值即可。
不难发现,刚才的做法实际上是有漏洞的。
Configuration
只在当前线程上下文起作用,调用线程池实际上就脱离了当前上下文。漏洞 1
可以通过人为规范避免。Kotlin 在语法上避免了 3
问题。
针对问题 2
,要执行 suspend 函数比较困难,但是也不是不能做。首先,通过 runBlocking
使得当前线程处于阻塞状态,不允许安排其他协程任务(但遗憾的是你的函数也不肯在当前线程跑)。然后,使用 asContextElement 使协程上下文携带某种信息。但这意味着前面的代码几乎都得为此重写,总体上还是比较难做的。
如图所示,在 IDEA 2019.X 以及 Android Studio 3.6.X, Gradle 在编译项目过程中所有中文错误提示均乱码。
最近在学习 Kotlin 这门编程语言,不得不感叹 Kotlin 这语言是真的骚。
interface BotUser {
val id: Long
val name: String
val description: String
companion object {
operator fun invoke(
id: Long,
name: String = "",
description: String = ""
): BotUser {
return BotUserImpl(id, name, description)
}
}
}
class BotUserImpl(...) { ... }
如果你这样做的话,可以让接口或者抽象类仿佛看起来能够被实例化一样(看似调用“构造方法”,实际上是调用了 invoke() )。
用途:项目中途将某个类抽象为一个接口,并将原有类作为此接口的默认实现,这样可以做到源代码的兼容(对 Java 代码以及二进制仍不兼容,毕竟本质不同)。
代码来自我的项目 “MoeCraftBotNG”
一般可以重载 get()
set()
来使对象能够按数组、字典一样去操作。
为对象编写 contains()
方法时(判断某个元素是否“属于”此对象),可以顺便为此方法加个 operator
关键字可以让 in
关键字支持这个对象
然而操作符重载还是慎用为妙,众所周知这个特性就是被 C++ 玩坏的,当你 java 甚至以不支持操作符重载为荣。好在 Kotlin 在这方面也比较节制。
如果想让你的 suspend 函数被 Java 用户友善地调用,防止你被人砍,你可以:
用 Kotlin 编写一个类:
class Coroutines {
/**
* 获取在 Java 代码中调用 Kotlin Suspend 函数所需的最后一个参数 (Continuation)
* @param onFinished 当suspend函数执行完毕后所调用的回调。若 Throwable 不为 null 则说明执行失败。否则为执行成功
* @param dispatcher 协程执行线程的类型。可以为 Dispatchers.Default(CPU密集型) Dispatchers.Main(主线程) Dispatchers.IO(IO密集型)
*/
@JvmOverloads
fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Default): Continuation<R> {
return object : Continuation<R> {
override val context: CoroutineContext
get() = dispatcher
override fun resumeWith(result: Result<R>) {
//注意 Result 是 inline class,不可直接给出去
onFinished.accept(result.getOrNull(), result.exceptionOrNull())
}
}
}
}
kotlin suspend 函数的调用难点在于最后一个参数,这个参数是 suspend 函数自动生成的,但是在 Java 方面处理起来却十分棘手。你可以提供这样的一个类来生成最后一个参数的值。
然后在 Java 中,就可以这样调用了:
Coroutines coroutines = new Coroutines();
//假设一个 suspend fun login(username: String, password: String): RequestResult,位于 object UserUtils
UserUtils.INSTANCE.login("user", "pass", coroutines.getContinuation(
(result, throwable) -> {
//suspend fun执行结束的回调
System.out.println("Coroutines finished");
System.out.println("Result: " + result);
System.out.println("Exception: " + throwable);
}
)
);
另外,也可以使用 org.jetbrains.kotlinx:kotlinx-coroutines-jdk8
,这个库可以让 suspend fun 返回 CompletableFuture<>
以便在 java 使用
fun doSomethingAsync(): CompletableFuture<List<MyClass>> =
GlobalScope.future { doSomething() } //返回 CompletableFuture 包装的 suspend fun doSomething()
Kotlin 有很多实用的内置函数,只提几个。
run() 字面意思,用于执行一个任意代码块,并返回代码块的返回值
run() 有两种,签名如下:
public inline fun <R> run(block: () -> R): R
public inline fun <T, R> T.run(block: T.() -> R): R
第一种 run() 在为构造函数委托传递参数时特别有用,例如有一个类和两个次构造函数
class ManagedJavaProperties(val inputStream: InputStream, val outputStream: OutputStream? = null) {
constructor(file: File): this(file.inputStream(), file.outputStream())
constructor(fileName: String): this(???) //需要进行处理才能委托给其他构造函数
}
第二个次构造函数中需要对 fileName 进行一些处理,此时需要 run() 登场了:
class ... {
constructor(fileName: String): this(
kotlin.run {
val file = File(fileName)
if (!file.exists()) {
file.createNewFile()
}
file
}
)
}
第二种 run() 则是将调用者当作 this 传递给 lambda,除此之外和第一种完全相同
run() 也可以用于防止代码块内变量污染当前作用域。(类似于 Java 的 { }
)
Kotlin 的 @Suppress
注解不仅可以抑制警告,还可以抑制任何错误。具体的错误名字可以到 kotlin 编译器项目按错误提示寻找。
如果手机已经Root并且开了ADB调试并且以前授权过要连接的电脑,则直接连该电脑即可。
如果手机未Root但刷入了TWRP或CWM等Recovery,则需要重启后进recovery,然后连电脑。
然后运行以下命令即可:
adb shell
rm /data/system/gallery_private.key
rm /data/system/gatekeeper.password.key
rm /data/system/gatekeeper.pattern.key
rm /data/system/gesture.key
reboot
也许会提示文件不存在,忽略即可。
重启后手机将没有锁屏密码。
什么?你加密了手机分区?格式化吧。
博主在刷入Oneplus3 H2OS Openbeta 7后再刷LineageOS遇到了“Comparing TZ version TZ.BF.X.X-X.X.XXXX to TZ.BF.X.X-X.X.XXXX assert failed”报错
原因是当前手机固件版本过低或过高,第三方ROM不兼容。
我们知道,原厂ROM刷机包一般都会带有完整的固件,位于刷机包内 firmware-update 文件夹,可以直接提取这里面的固件,然后使用 fastboot flash 分区名 固件路径,其中分区名就是你看到的文件名再去掉扩展名
于是博主准备使用fastboot刷入固件,但又遇到了以下报错:
fastboot flash tz tz.mbn
target reported max download size of 440401920 bytes
sending ‘tz’ (1628 KB)…
OKAY [ 0.080s]
writing ‘tz’…
FAILED (remote: Partition flashing is not allowed)
finished. total time: 0.118s
(心情复杂.jpg)
然后博主突然想到recovery也可以刷入固件,抱着试一试的心态,做了一个固件刷机包
wow! 真的成功了哎,现在可以刷LineageOS了
刷入某些ROM可能导致手机在引导时弹出DM-Verity警告,警告内容为
The dm-verity is not started in enforcing mode ….
修复方法:重开DM-Verity然后再禁用。
操作步骤:
1.连电脑,进fastboot
2.输入以下代码:
fastboot oem disable_dm_verity
fastboot oem enable_dm_verity
fastboot oem disable_dm_verity
fastboot reboot
本人用的是mi4,刷入秋叶随风的合并分区包以后再刷6.0的包提示“This package is for device: cancro this device is .”
看样子是不认设备了,就想到了直接从脚本里面干掉这个检测
[这里是一张该问题的截图,然而博主在将博客从WP人工迁移到Hexo时找不到了]