Android高效进阶:从数据到AI【2.0】

第 4 章 Android 工具应用进阶

Android 的出现加速了移动互联网的发展,它以一种更好的方式进行人机互动。在Android 工具应用领域,每年优质的应用程序层出不穷。本章将选取几个工具应用(游戏加速器、近场传输、微信清理和 Google 安装器)来进行进阶介绍。

4.1 游戏加速器

随着手游的火爆,很多游戏用户都希望能更快地体验游戏的互动感觉,因此游戏加速器也就应运而生了。

4.1.1 游戏加速器的使用场景

市面上比较火爆的《王者荣耀》和《绝地求生:大逃杀》两款游戏吸引了一大波用户一起玩,在游戏移动端化后,由于整个网络环境的不确定性以及手机性能的不一致,导致游戏卡顿、掉帧、延迟和丢包严重的现象层出不穷,极其影响游戏体验。因此一大波移动游戏加速器就应运而生了,其中有老牌厂商讯游加速器和 UU 加速器,也有新晋玩家海豚加速器和 8LAG加速器。它们的目的都是让用户在玩游戏时拥有一个良好的不间断的体验。游戏加速器分两个层面加速,一个层面是手机自身的加速,另一个层面是网络连接的加速。

4.1.2 基于性能的加速实现

性能相关的加速实现主要分为以下 3 类,它们的目的都是确保在用户打开游戏之前当前手机处于最佳的性能状态。

1.系统缓存清理

清理系统当前存在的缓存数据(主要是清除自己和第三方应用的无用缓存),主要是保证内存空间充足,提升 I/O 的读/写速度。

清除自身应用的缓存,主要是删除如下两个路径下的缓存文件。 context.getExternalCacheDir().getAbsolutePath(); /storage/emulated/0/Android/data/包名/cache——这是应用程序外部缓存路径。

 context.getCacheDir().getAbsolutePath(); /data/data/包名/cache——这是应用程序内部缓存路径。

清除第三方应用的无用缓存,首先最重要的是声明对应的权限:

1. <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
2. <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />

通过 PackageManager.getPackageSizeInfo 获取某个包名的应用自定义的缓存大小:
 

1. private void getCacheSize(PackageInfo packageInfo) {
2. try {
3. //通过反射获取当前的方法
4. Method method = PackageManager.class.getDeclaredMethod
("getPackageSizeInfo", String.class, IPackageStatsObserver.class);
5. method.invoke(mPackageManager, packageInfo.applicationInfo.
packageName, new MyIPackageStatsObserver(packageInfo));
6. } catch (Exception e) {
7. e.printStackTrace();
8. }
9. }

MyIPackageStatsObserver 是跨进程通信移动端的系统回调方法,相关的 aidl 文件是系统的IPackageStatsObserver.aidl 和 PackageStats.aidl 文件,需要将这两个文件从系统中拿出来,放到指定的文件夹( android.conent.pm)下,如图 4-1 所示。

具体的实现代码如下:
1. private class MyIPackageStatsObserver extends IPackageStatsObserver.Stub {
2. private PackageInfo packageInfo;
3.
4. public MyIPackageStatsObserver(PackageInfo packageInfo) {
5. this.packageInfo = packageInfo;
6. }
7. @Override
8. public void onGetStatsCompleted(PackageStats pStats, Boolean
succeeded) throws RemoteException {
9. // cacheSize 表示当前包名的应用的缓存大小
10. long cacheSize = pStats.cacheSize;
11. }
12. }

当 获 取 到 的 缓 存 大 小 不 为 0 时 , 清 理 缓 存 ( 通 过 反 射 调 用 PackageManager#freeStorageAndNotify):
 

1. public void cleanAllCache() {
2. try {
3. StatFs stat = new StatFs(Environment.getDataDirectory().
getAbsolutePath());
4. Method mFreeStorageAndNotifyMethod = mPackageManager.getClass().
getMethod(
5. "freeStorageAndNotify", long.class, IPackageDataObserver.
class);
6. mFreeStorageAndNotifyMethod.invoke(mPackageManager,
7. (long) stat.getBlockCount() * (long) stat.getBlockSize(),
8. new MyIPackageDataObserver()
9. );
10. } catch (NoSuchMethodException e) {
11. e.printStackTrace();
12. } catch (InvocationTargetException e) {
13. e.printStackTrace();
14. } catch (IllegalAccessException e) {
15. e.printStackTrace();
16. }
17. }

清理的结果会通过 MyIPackageDataObserver 进行回调:
 

1. class MyIPackageDataObserver extends IPackageDataObserver.Stub {
2. public MyIPackageDataObserver() {
3. }
4. @Override
5. public void onRemoveCompleted(String packageName, booleansucceeded)
throws RemoteException {
6. //packageName 表示清除的是哪个应用程序, succeeded 表示是否清理成功
7. }
8. }

2.后台进程清理

目 前 比 较 常 规 、 常 用 的 方 案 是 用 ActivityManager 的 killBackgroundProcesses 或 者forceStopPackage 方法去清理某个 App 对应的进程。前一个方法虽然能暂时停止进程,但是实际上进程重启的概率极大,几乎无任何实质作用;后一个方法虽然可以完全杀死进程,但只对系统级应用才有效,因此对应用层的 App 来说,想要以常规方法彻底杀死某个应用进程是几乎不可能的事情。于是只能另辟蹊径,利用具有系统权限的应用帮助我们清理相关的进程。

注意, Android 手机上每一个已安装的 App 都有一个系统级别的 App 详情页面,而每个App 详情页面中都有一个类似强行停止的按钮,以微信为例如图 4-2 所示。

当用户单击“强行停止”按钮时,实际上底层会通过类似“ am kill 包名”的方式彻底杀死某个 App 的相关进程,效果与 forceStopPackage 方法类似。由于这些 App 详情页面是系统页面,因此完全不需要声明对应的权限。现在可以将问题简化为以下两点。

( 1)进入某个 App 的系统级详情页面

在开发过程中很多时候都会用 adb 命令去启动一个系统页面,启动一个 App 系统级详情页面的 adb 命名如下:

1. adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS

-d package:应用包名

因此,在运行时,可以通过 Runtime.getRuntime().exec 的方式执行一个 adb 命令,具体的操作如下面的伪代码所示:

1. process = Runtime.getRuntime().exec("命令");
2. os = new DataOutputStream(process.getOutputStream());
3. os.flush();
4. //获取输出结果
5. result = process.waitFor();

( 2)帮助用户单击“强行停止”按钮

帮助用户单击“强行停止”按钮,关键在于怎么帮。我们知道 Android 系统中有辅助服务( AccessibilityService),其目的是帮助那些具有视觉、身体或年龄相关限制的用户更轻松地使用 Android 设备和应用。除此之外,我们还可以使用辅助服务对一些人工操作进行自动化处理,从而将人从这些无聊、烦琐的重复性操作中解放出来。此处就是为了简化人工操作,比如,帮用户单击“强行停止”按钮。

首先定义一个继承 AccessibilityService 的服务(在这里将其命名为 MyAccessibilityService),并在 AndroidManifest 中进行声明:

1. <service
2. android:name=".MyAccessibilityService"
3. android:description="辅助服务功能的具体描述信息"
4. android:label="辅助服务的名字"
5. android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
6. <intent-filter>
7. <action android:name="android.accessibilityservice.
AccessibilityService"/>
8. </intent-filter>
9.
10. //辅助服务的配置
11. <meta-data
12. android:name="android.accessibilityservice"
13. android:resource="@xml/accessibility_service_config"/>
14. </service>

配置辅助服务参数(在 res/xml 文件夹下新建 accessibility_service_config.xml 文件), Android 官方网站上有详细的配置指导:
 

1. <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
2. android:description="辅助服务功能的具体描述信息"
3. android:accessibilityEventTypes="typeViewScrolled|typeWindowContent
Changed|typeWindowStateChanged"
4. android:accessibilityFlags="flagIncludeNotImportantViews|flagReport
ViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|
flagRequestFilterKeyEvents"
5. android:canPerformGestures="true"
6. android:canRequestFilterKeyEvents="true"
7. android:canRetrieveWindowContent="true"
8. android:accessibilityFeedbackType="feedbackAllMask"
9. android:notificationTimeout="100"
10. />

在自定义的 MyAccessibilityService 中做监听,并且实现协助单击操作,主要需要关注继承AccessibilityService 类并覆盖该类中的以下方法。

 onServiceConnected:系统成功连接到辅助服务时调用,可以执行任何一次性设置步骤。

 onAccessibilityEvent : 当 系 统 检 测 到 辅 助 服 务 指 定 的 事 件 过 滤 参 数 匹 配 的

AccessibilityEvent 时调用。通常需要在该方法中根据 AccessibilityEvent 做出判断并执

行一些处理操作。

当用户界面上发生了服务需要关注的事件时,系统就会发送 AccessibilityEvent 事件,并传递给 onAccessibilityEvent 方法。

在用户开启辅助服务后,自动化单击按钮的流程分析如下。

首先通过以下命令获取需要 App 详情页面的关键类名(当前手机顶层的 Activity):

adb shell dumpsys activity top | grep ACTIVITY

在切换页面后,触发 onAccessibilityEvent 中的 TYPE_WINDOW_STATE_CHANGED 事件。在此事件中通过 event.getClassName 获取当前窗口的类并与目标类匹配,匹配成功后,通过整个根节点根据对应的文案获取屏幕中的“强行停止”的 View 组件。

1. getRootInActiveWindow().findAccessibilityNodeInfosByText(“强行停止”)

然后执行相应的单击操作,即 performAction 操作:

1. AccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容