Robolectric 4.8.2修改进程名/私有静态变量

Roboletric 4.8.2 修改进程名:

Roboletric 4.8.2 修改私有静态变量:

如果报错:

则修改方式如下:

完整的例子如下:

参考链接


Using PowerMock - robolectric/robolectric Wiki

ubuntu 20.04升级到ubuntu 22.04报错“update-alternatives: 错误: /var/lib/dpkg/alternatives/mpi 损坏:次要链接与主链接 /usr/bin/mpicc 相同”

ubuntu 20.04 升级到 ubuntu 22.04 报错,报错内容如下:

解决方法:

Android Studio升级ArcticFox后单元测试报错“Test events were not received”

Android Studio升级ArcticFox后单元测试报错“Test events were not received”,如下图:

解决方法:目前最简单的方法就是升级到 Android Studio Chipmunk|2021.2.1 Path 2,然后对项目整体执行一次Clean,删除之前的测试用例并重建。

操作步骤如下图:

1. 清理工程代码,移除缓存

2. 选中出问题的测试用例的编辑配置

3. 移除之前的测试用例,这样可以迫使测试用例重建

4. 再次执行测试用例

参考链接


Android获取当前进程的名称

通过 ActivityManager 获取进程名

缺点:

  1. am.getRunningAppProcesses()需要跨进程通信,效率不高
  2. ActivityManager.getRunningAppProcesses() 有可能调用失败,虽然概率不高但是当用户数量达到一定级别还是有概率失败的。
  3. Application.getProcessName() 只能Api28 以上的系统调用

最优解:

参考链接


android 获取当前进程的名称

Collections.singletonList/Collections.emptyList/Collections.emptyMap

最近在研究 Flutter pigeon 例子 的时候,发现如下实例代码:

对其中的 Collections.singletonList(result) 比较感兴趣,研究了一下,发现还是比较有意义的。

Collections.singletonList()

这个方法主要用于只有一个元素的优化,减少内存分配,无需分配额外的内存,可以从SingletonList内部类看得出来,由于只有一个element,因此可以做到内存分配最小化,相比之下ArrayListDEFAULT_CAPACITY=10个。

下面是SingletonList静态类的定义

上面的源码中可以看到,静态类中并没有重新add、delete、set等方法。所以通过Collections.singletonList初始化的List是不能执行上述方法的。

Collections.emptyList()

Collections.emptyList在日常开发中也比较常用,如果一个方法需要返回一个空List,并且后续不用再新增元素进去,我们完全可以直接返回Collections.emptyList()而不是new ArrayList;这样不用每次都去创建一个新对象。

EMPTY_LIST如下

Collections中其他类似方法

参考链接


另一种绕过Android P以上非公开API限制的办法

去年发布的 Android P 上引入了针对非公开 API 的限制,对开发者来说,这绝对是有史以来最重大的变化之一。前天 Google 发布了 Android QBeta 版,越来越多的 API 被加入了黑名单,而且 Google 要求下半年 APP 必须 target 28,这意味着现在的深灰名单也会生效;可以预见,在不久的将来,我们要跟大量的 API 说再见了。

去年我给出了一种绕过Android P对非SDK接口限制的简单方法,经验证,这办法在 Android Q 的 Beta 版上依然能正常使用。虽然这个方法需要进行内存搜索,理论上有可能失败,但实际上它曾在 VirtualXposed 和 太极 中得到了较为广泛的验证,从未收到过由于反射失败而导致问题的反馈。而且据我所知,有若干用户量不少的 APP 在线上使用 FreeReflection 库,想来应该也是没有问题的吧。

不过今天,我打算给出另外一种绕过限制的办法。这个办法目前来说是最优方案,我个人使用了一个多月,不存在任何问题。

上次分析系统是如何施加这个限制 的时候,我们提到了几种方式,最终给出了一种修改 runtime flag 的办法;其中我们提到,系统有一个 fn_caller_is_trusted 条件:如果调用者是系统类,那么就允许被调用。这是显而易见的,毕竟这些私有 API 就是给系统用的,如果系统自己都被拒绝了,这是在玩锤子呢?

也就是说,如果我们能以系统类的身份去反射,那么就能畅通无阻。问题是,我们如何以「系统的身份去反射」呢?一种最常见的办法是,我们自己写一个类,然后通过某种途径把这个类的 ClassLoader 设置为系统的 ClassLoader,再借助这个类去反射其他类。但是这里的「通过某种途径」依然要使用一些黑科技才能实现,与修改 flags / inline hook 无本质区别。

以系统类的身份去反射 有两个意思,1. 直接把我们自己变成系统类;2. 借助系统类去调用反射。我们一个个分析。

「直接把我们自己变成系统类」这个方式有童鞋可能觉得天方夜谭,APP 的类怎么可能成为系统类?但是,一定不要被自己的固有思维给局限,一切皆有可能!我们知道,对APP来说,所谓的系统类就是被 BootstrapClassLoader 加载的类,这个 ClassLoader 并非普通的 DexClassLoader,因此我们无法通过插入 dex path的方式注入类。但是,Android 的 ART 在 Android O 上引入了 JVMTI,JVMTI 提供了将某一个类转换为 BootstrapClassLoader 中的类的方法!具体来说,我们写一个类暴露反射相关的接口,然后通过 JVMTI 提供的 AddToBootstrapClassLoaderSearch将此类加入 BootstrapClassLoader 就实现目的了。不过,JVMTI 要在 release 版本的 APP 上运行依然需要 Hack,所以这种途径与其他的黑科技无本质区别。

第二种方法,「借助系统的类去反射」也就是说,如果系统有一个方法systemMethod,这个systemMethod 去调用反射相反的方法,那么systemMethod毋庸置疑会反射成功。但是,我们从哪去找到这么一个方法给我们用?事实上,我们不仅能找到这样的方法,而且这个方法能帮助我们调用任意的函数,那就是反射本身!可能你已经绕晕了,我解释一下:

  1. 首先,我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法

  2. 然后,我们通过刚刚反射拿到元反射方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法。

伪代码如下:

到这里,我们已经能通过「元反射」的方式去任意获取隐藏方法或者隐藏 Field 了。但是,如果我们所有使用的隐藏方法都要这么干,那还有点小麻烦。在 上文中,我们后来发现,隐藏 API 调用还有「豁免」条件,具体代码如下:

只要 IsExempted 方法返回 true,就算这个方法在黑名单中,依然会被放行然后允许被调用。我们再观察一下IsExempted方法:

继续跟踪传递进来的参数 runtime->GetHiddenApiExemptions() 发现这玩意儿也是 runtime 里面的一个参数,既然如此,我们可以一不做二不休,仿照修改 runtime flag 的方式直接修改 hidden_api_exemptions_ 也能绕过去。但如果我们继续跟踪下去,会有个有趣的发现:这个API 竟然是暴露到 Java 层的,有一个对应的 VMRuntime.setHiddenApiExemptions Java方法;也就是说,只要我们通过 VMRuntime.setHiddenApiExemptions 设置下豁免条件,我们就能愉快滴使用反射了。

再结合上面这个方法,我们只需要通过 「元反射」来反射调用 VMRuntime.setHiddenApiExemptions 就能将我们自己要使用的隐藏 API 全部都豁免掉了。更进一步,如果我们再观察下上面的 IsExempted 方法里面调用的 DoesPrefixMatch,发现这玩意儿在对方法签名进行前缀匹配;童鞋们,我们所有Java方法类的签名都是以 L开头啊!如果我们把直接传个 L进去,所有的隐藏API全部被赦免了!

详细代码在这里:https://github.com/tiann/FreeReflection

理论上讲,这个方案不存在兼容性问题。即使 ROM 删掉了 setHiddenApiExemptions 方法,我们依然可以用「元反射」的方式去反射隐藏API,并且所有的代码加起来不超过30行!当然,如果 Google 继续改进验证隐藏API调用的方法,这个方式可能会失效,但是目前的机制没有问题。

参考链接


Android多用户模式下获取当前UserId的方式

1. Linux uid/gid

Linux下的用户id(uid)和群组id(gid)。Linux是多用户系统,每个用户都拥有一个uid,这个uid由系统和用户名做映射绑定。同时,为了便于用户管理(譬如管理文档权限),Linux引入了群组的概念,可以将多个用户归于一个群组。每一个群组拥有一个群组id(gid)。 
root用户:Linux下的唯一的超级用户,拥有所有的系统权限。root用户所在的组就是root组。

2. Android uid(4.2(API Level 17))

Android 4.2开始支持多用户。Linux的uid/gid多用户体系已经被用在App管理上。

Android重新开发了一套多用户体系,在UserManagerService中管理,PackageManagerService和ActivityManagerService中也有相关逻辑。Android的多用户可以做到不同用户的应用的物理文件级(数据)的区分,以实现不同用户有不同的壁纸、密码,以及不同的应用等。

例如:在一个有两个用户(用户id分别为0和10)的安卓设备上,在用户10下安装一个应用,此时,在0下是看不到这个应用的。

从data/system/packages.xml查看此应用的uid:userId=”10078”

Process.myUid()得到uid为”1010078”

Process.myUserHandle()得到”userHandle{10}”

在另一个用户0下安装此应用。

查看packages.xml,看到uid没有变化10078

Process.myUid()得到uid为”10078”

Process.myUserHandle()得到”userHandle{0}”

adb shell进入命令行,分别查看data/user/0和data/user/10下面此应用的数据区:

用户0: 

 

用户10:

 

可以看到,实际上应用在内部虽然有多用户,但只有一个uid,在不同的用户下,通过uid和用户id合成一个新的uid,以保证在每个用户下能够区分。 

(可以看到文件拥有者是u0_a78,所在群组为u0_a78。从data/system/packages.xml根据包名查看此应用信息,可以看到:userId=”10078”。)

3. android.os.UserHandle

这个类对外提供有关多用户的接口。 从里面的一些api代码可以看到uid在多用户下的处理逻辑:

多用户支持开关: 

注意一个api getUid()。这就清楚了,将用户id 10作为第一个参数,packages.xml中记录的该应用的uid 10078作为第二个参数传入,得到了这个应用在10用户下的uid——1010078! 


 

通过应用的uid得到当前用户的userId,以上过程的逆过程: 

 

从另一个核心的api myUserId()更能清楚地看到应用uid和用户id的关系: 

 

当一个应用使用UserHandle.myUserId()来获取当前的用户id的时候,其实就是从他自己的进程得到应用的uid,然后通过上述逻辑计算出当前的用户id。 

从Process.myUserHandle()也能清楚地看到这个逻辑:

从概念和API命名上,确实有些混乱,但Android也情非得已,Process的API Level是1,UserHandle的API Level是17,可见在最初的Android上面,已经将Linux uid/gid给了应用id了,当时应该也没有考虑Android有一天需要支持多用户。直到4.2(API Level 17),引入了多用户时,已经是若干年过去了,Process已经被无数的开发者使用,无法改变。只能接受这个概念上混淆了。 

可以用如下的几点来简单地澄清这些id概念: 

  1. Process中的xxid相关的概念和API是关于应用id的。 
  2. UserHandle中的xxid相关的概念和API是关于Android用户id的。 
  3. Process有接口得到UserHandle实例。

4. 应用层获取UserId

有时候,我们需要根据不同的用户ID来进行兼容性处理,比如魅族的系统在分身模式下,生物识别相关的Keystore调用(setUserAuthenticationRequired(true))会抛出异常。

我们需要针对这种情况进行兼容性处理,已知的是,魅族的应用分身模式下,用户的ID一定是 999

Process.myUserHandle() 可以得到 UserHandle 对象,但是却不能直接从 UserHandle 对象中获取到用户ID

目前有三种已知的做法

  1. android.os.Process.myUid()/100000 这行代码的原理是依据 Process.myUserHandle() 实现的代码进行逆向操作来获取到真正的用户ID,如果不了解源代码的人会感觉莫名其妙。不需要特殊权限,但是总感觉不够优雅。

  2. ActivityManager.getCurrentUser() 需要申请权限,需要系统应用,需要反射调用。感觉更不优雅了。

  3. UserHandle.myUserId() 不需要特殊权限,需要反射调用。这个感觉也不够优雅。但是当看到androidx.os.UserHandleCompat 也是通过反射调用这些函数的时候,瞬间感觉无所谓了。
    代码参考如下:

参考链接


Windows 10系统VirtualBox无法进入系统,日志报错“HM: HMR3Init: Attempting fall back to NEM: VT-x is not available”

Windows 10上使用 Linux 子系统的时候,无法成功启用。根据官方文档 旧版 WSL 的手动安装步骤 之后,依旧没效果,反倒是VirtualBox无法进入系统了。

观察日志,报错信息如下:

Intel CPU

AMD CPU

解决方案如下:

1.使用管理员启动命令行.

2. 执行如下命令:


某些电脑需要额外执行如下命令:


3. 重启电脑

参考链接


GTX760支持4k分辨率吗?

支持的,所有开普勒架构的桌面显卡,都可以支持到4K分辨率的输出,包括GTX650等极端开普勒架构显卡。

以下是GTX760最新的官方参数

从技术支持和特性描述可以看出,GTX760原生支持4K输出,当然也就包括了各个子生厂商生产出的各种GTX760。并且可以通过DP接口输出4K@60Hz,通过HDMI输出4K@24Hz~30Hz,HDMI下的刷新率不如DP是因为HDMI的版本带来的带宽问题,所以如果你连接的是60Hz刷新率的4K显示器,那么建议是用DP接口进行连接。

之所以一些品牌的GTX760没写支持4K,是因为在GTX760发布的时候,4K还并不盛行,所以一些产品的资料里就并没注明支持4K,后来这些资料也并未进行更新,所以可能会产生误导,实际上是支持的。

只是GTX760的4K游戏性能较弱,如果你打算用GTX760在4K下进行游戏,那么可能需要调低不少特效,才能保证一定的画面流畅度。

参考链接


微星gtx760支持4k分辨率吗?

ubuntu 20.04.4系统升级后全部应用图标都无法显示

ubuntu 20.04.4系统执行升级命令后全部应用图标都无法显示,系统变成如下图:

很多应用也无法打开了,点击之后无任何反应。

查看系统日志可以看到如下错误信息:

这个原因是由于 gdk-pixbuf 的改动导致的系统 BUG,只需要重新生成一下应用图标缓存即可。

执行如下命令:

参考链接