SystemUI 解密悬浮通知上滑后不显示悬浮通知的原因

本文详细探讨了Android系统中悬浮通知在上滑后消失不显示的问题,原因是进入了小睡模式(snooze)。内容包括小睡模式的判断逻辑,悬浮通知上滑后的处理流程,并提供了自定义白名单以改善用户体验的思考。

不积跬步无以至千里

一.前摘

       在使用手机时,你会发现来了一条悬浮通知,然后上滑此悬浮通知消失,当你再接收到此悬浮通知时,但是并没有显示悬浮通知,其实细心的能发现过一会就能恢复了,其实你想想这也是合理的,目前你着急把这个悬浮通知上滑了,肯定是此时不想被打扰啊,过一段时间后我再让你能看见它,合情合理。

二.正文

       其实这里是进入了一种模式:小睡模式(snooze),然后进入这种模式后则会不再显示悬浮通知了,过段时间就会把这个包名的名单清楚掉,我们这里通过逻辑讲一下吧。

1.获取是否为小睡模式的通知

①.判断通知是否为悬浮通知

代码路径:

app/src/com/android/systemui/statusbar/NotificationEntryManager.java

public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
        if (!mUseHeadsUp || mPr
<think> 我们面对的核心问题:悬浮窗Service如何调用加密DEX文件中的方法。 结合用户之前的问题(动态加载DEX替换MainActivity)和本次需求,我们聚焦于“动态加载加密DEX并执行其中方法”。 步骤分解: 1. 准备加密的DEX文件:将包含目标方法的DEX文件加密(例如AES加密),存放在assets或内部存储。 2. 动态解密并加载DEX:在悬浮窗Service启动时,解密DEX文件并加载到内存中。 3. 创建DexClassLoader:使用解密后的DEX文件路径创建DexClassLoader。 4. 反射调用方法:通过DexClassLoader加载目标类,并反射调用其方法。 注意事项: - Android 7.0以上文件访问限制(FileProvider)[^2] - 避免类加载器冲突 - 资源访问问题(如悬浮窗需要资源时) 具体实现步骤: 一、解密DEX文件并写入存储 ```java // 在Service的onCreate或适当位置 private void loadDecryptedDex() { try { // 1. 从assets读取加密的DEX文件 InputStream is = getAssets().open("encrypted.dex"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { bos.write(buffer, 0, len); } byte[] encryptedDex = bos.toByteArray(); is.close(); bos.close(); // 2. 解密(这里以AES解密为例,密钥需安全保存) byte[] decryptedDex = decrypt(encryptedDex, YOUR_SECRET_KEY); // 3. 写入内部存储 File dexInternalStoragePath = new File(getDir("dex", Context.MODE_PRIVATE), "decrypted.dex"); FileOutputStream fos = new FileOutputStream(dexInternalStoragePath); fos.write(decryptedDex); fos.close(); // 4. 创建DexClassLoader String optimizedPath = getDir("odex", Context.MODE_PRIVATE).getAbsolutePath(); DexClassLoader dexClassLoader = new DexClassLoader( dexInternalStoragePath.getAbsolutePath(), optimizedPath, null, getClassLoader() ); // 5. 使用ClassLoader加载目标类并调用方法 Class<?> clazz = dexClassLoader.loadClass("com.example.dex.MyClass"); Method method = clazz.getDeclaredMethod("myMethod", Context.class); // 假设方法需要一个Context参数 Object instance = clazz.newInstance(); method.invoke(instance, this); // 传入Service的Context } catch (Exception e) { e.printStackTrace(); } } // AES解密方法示例 private byte[] decrypt(byte[] data, byte[] key) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return cipher.doFinal(data); } ``` 二、悬浮窗Service中注意事项 1. 权限申请:需要申请悬浮窗权限(SYSTEM_ALERT_WINDOW) 2. 文件存储位置:使用内部私有目录,避免权限问题 3. 兼容性:注意Android 7.0及以上使用FileProvider访问文件(本例中由于是私有目录,暂时需要FileProvider) 三、调用加密DEX中的方法扩展 - 如果方法需要返回结果,则通过invoke的返回值获取。 - 如果方法需要更新UI,确保在悬浮窗的View中运行在主线程。 四、安全性增强 1. 密钥存储:建议使用Android KeyStore系统存储解密密钥。 2. 防止DEX被二次提取:解密后立即使用,并考虑在内存中清除敏感信息,或者将DEX加载到内存后删除解密文件。 五、资源加载问题 若加密DEX中包含资源(如图片、布局),则需要额外处理资源加载: ```java // 创建AssetManager AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, apkPath); // apkPath为包含资源的APK路径 // 创建Resources Resources pluginResources = new Resources(assetManager, getResources().getDisplayMetrics(), getResources().getConfiguration()); ``` 但注意:悬浮窗的View通常由主应用创建,若需要显示插件中的资源,可通过上述方法获取资源ID,但需要避免资源ID冲突(使用同的Resources对象)。 六、可能遇到的问题 1. 类找到:确保DexClassLoader的父类加载器设置正确(通常使用当前类加载器)。 2. 方法调用异常:检查方法的参数类型和数量是否匹配。 3. Android版本兼容:在Android 10及以上,对文件访问有更严格限制,务必使用内部私有目录。 七、优化建议 1. 异步加载:DEX解密和加载可能耗时,应放在子线程执行,避免ANR。 2. 错误处理:做好异常捕获,避免Service崩溃。 总结:通过动态加载解密后的DEX,悬浮窗Service可以调用其中定义的方法。重点在于安全地解密、正确地加载和反射调用。 §§相关问题§§ 1. 如何安全地存储用于解密DEX的密钥? 2. 动态加载的DEX如何访问宿主应用中的类? 3. 如何解决动态加载的资源与宿主资源ID冲突的问题? 4. 在Android 10及以上,动态加载DEX有哪些额外限制? </think> 在 Android 应用中实现悬浮窗 Service 调用加密 DEX 文件中的方法,需要结合动态加载、DEX 解密和反射技术。以下是详细步骤和解决方案: --- ### 一、核心实现步骤 #### 1. **准备加密的 DEX 文件** ```java // 加密原始 DEX 文件(开发阶段完成) public void encryptDex(File inputDex, File outputEncrypted) { byte[] data = Files.readAllBytes(inputDex.toPath()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES")); Files.write(outputEncrypted.toPath(), cipher.doFinal(data)); } ``` #### 2. **悬浮窗 Service 动态加载解密 DEX** ```java public class FloatingService extends Service { private DexClassLoader dexClassLoader; @Override public void onCreate() { // 1. 从 assets 读取加密 DEX InputStream is = getAssets().open("encrypted.dex"); FileOutputStream fos = new FileOutputStream(getFileStreamPath("encrypted_temp.dex")); // 2. 解密 DEX decryptDex(is, fos); // 3. 创建 DexClassLoader File dexFile = getFileStreamPath("decrypted.dex"); File optDir = getDir("odex", MODE_PRIVATE); dexClassLoader = new DexClassLoader( dexFile.getAbsolutePath(), optDir.getAbsolutePath(), null, getClassLoader() ); } private void decryptDex(InputStream is, OutputStream os) { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES")); // ... 解密流程 ... } } ``` #### 3. **反射调用 DEX 中的方法** ```java // 在悬浮窗点击事件中调用 public void callEncryptedMethod() { try { // 1. 加载目标类 Class<?> targetClass = dexClassLoader.loadClass("com.example.EncryptedUtils"); // 2. 获取方法实例 Method showFloatMethod = targetClass.getDeclaredMethod( "showCustomFloat", Context.class, String.class ); // 3. 调用方法 Object instance = targetClass.newInstance(); showFloatMethod.invoke(instance, this, "Hello from encrypted DEX!"); } catch (Exception e) { Log.e("DEX_CALL", "Method call failed", e); } } ``` --- ### 二、关键技术点解析 #### 1. **DEX 安全处理** - **加密算法**:使用 AES-256 加密 DEX 文件,密钥存储在 AndroidKeyStore 中[^2] - **运行时解密**:在内存中解密,避免解密文件长期暴露 - **文件存储**:使用内部存储目录 `getDir()` 防止未授权访问 #### 2. **跨类加载器通信** - **类加载器继承关系**: ```mermaid graph TD A[BootClassLoader] --> B[PathClassLoader] B --> C[DexClassLoader宿主] C --> D[自定义DexClassLoader] ``` - **解决方法可见性**:通过 `getDeclaredMethod()` 获取私有方法,使用 `setAccessible(true)` 突破访问限制 #### 3. **资源与上下文传递** - **资源访问**: ```java // 加载插件资源 AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, dexFilePath); ``` - **上下文传递**:将被调用方法的第一个参数设计为 `Context` 类型,传入 Service 实例 --- ### 三、Android 版本适配方案 | Android 版本 | 注意事项 | 解决方案 | |-------------|---------|----------| | 7.0+ | FileUriExposedException | 使用 FileProvider[^2] | | 8.0+ | 后台服务限制 | 启动前台服务并显示通知 | | 10+ | 分区存储 | 使用 MediaStore 或 APP 私有目录 | | 12+ | 精确悬浮窗权限 | 添加 `<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>` | --- ### 四、完整调用流程 1. **初始化阶段**: ```mermaid sequenceDiagram 悬浮窗Service->>Assets: 读取加密DEX 悬浮窗Service->>Cipher: 解密DEX 悬浮窗Service->>DexClassLoader: 加载解密后的DEX ``` 2. **方法调用阶段**: ```mermaid sequenceDiagram 用户点击悬浮窗->>FloatingService: 触发点击事件 FloatingService->>DexClassLoader: loadClass("TargetClass") DexClassLoader-->>FloatingService: 返回Class对象 FloatingService->>反射API: getDeclaredMethod() 反射API-->>FloatingService: Method对象 FloatingService->>Method.invoke(): 执行目标方法 ``` --- ### 五、安全增强建议 1. **密钥管理**: ```java // 使用 AndroidKeyStore KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore" ); keyGenerator.init(new KeyGenParameterSpec.Builder( "dex_key", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .build()); ``` 2. **反调试措施**: ```java // 检测调试状态 if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { System.exit(1); } ``` 3. **DEX 校验**:在加载前计算 SHA-256 校验和验证完整性 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rom_Fisher

赠人玫瑰,手留余香。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值