Android Lolipop 屏蔽隐式Intent检查引发的错误

前言

Android Lolipoop以上版本屏蔽了隐式Intent启动Service,以前我们可以这么做:

Intent intent = new Intent("action.of.service");
context.startService(intent);

现在这样行不通了,会报错!根据Google指示,我们最少应该给Intent里面加上Service的包名:

Intent intent = new Intent("action.of.service");
intent.setPackage("packageName.of.service");
context.startService(intent);

好的,这样修改就妥妥的了。

偷懒

我们动动歪脑筋,在手握源码的情况下,为何不直接屏蔽隐式Intent验证机制呢?回到KitKat年代,一片和谐,多么美好。

过程

说干就干。start/stop/bind service时的Intent验证机制位于ContextImpl.java中。
可以看到也就是抛个异常,别的啥也没干嘛。于是我们动手:

private void validateServiceIntent(Intent service) {
    if (service.getComponent() == null && service.getPackage() == null) {
        if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
            IllegalArgumentException ex = new IllegalArgumentException(
                    "Service Intent must be explicit: " + service);
            throw ex; <--- 我们轻松将其注释
        } else {
            Log.w(TAG, "Implicit intents with startService are not safe: " + service
                    + " " + Debug.getCallers(2, 3));
        }
    }
}

之后新编framework.jar替换原来的,重启。

问题

一切并未按预期发展,更改后的系统开机时不断重启,检查log,发现出错的调用堆栈与DevicePolicyManagerService.java和KeyChain.java两个文件有关。

DevicePolicyManagerService

代码接收开机广播,启动一个AsyncTask。

BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
    ......
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)
                || KeyChain.ACTION_STORAGE_CHANGED.equals(action)) {
            new MonitoringCertNotificationTask().execute(intent);
        }
    }
}

AsyncTask中做了一些工作,其中会调用KeyChain进行绑定Service操作。

private void manageNotification(UserHandle userHandle) {
    ......
    // Call out to KeyChain to check for user-added CAs
    boolean hasCert = false;
    try {
        KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
        try {
            if (!kcs.getService().getUserCaAliases().getList().isEmpty()) {
                hasCert = true;
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
        } finally {
            kcs.close();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } catch (RuntimeException e) {
        Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
    }
    ......
}

KeyChain

public static KeyChainConnection bindAsUser(Context context, UserHandle user)
        throws InterruptedException {
    if (context == null) {
        throw new NullPointerException("context == null");
    }
    ......
    Intent intent = new Intent(IKeyChainService.class.getName());
    ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
    intent.setComponent(comp); <---- 出错时,这个ComponentName为空
    boolean isBound = context.bindServiceAsUser(intent,
                                                keyChainServiceConnection,
                                                Context.BIND_AUTO_CREATE,
                                                user);
    if (!isBound) {
        throw new AssertionError("could not bind to KeyChainService");
    }
    return new KeyChainConnection(context, keyChainServiceConnection, q.take());
}

可以看到KeyChain中视图绑定一个Service,且在出错的调用链上Component为空,实际上是隐式Intent绑定Service。
这是Google自己也有问题啊。。。。。。

分析

  1. 隐式Intent验证机制生效,系统正常。
  2. 隐式Intent验证机制被屏蔽,系统system_server进程报错,定位错误位于以上启动Service代码。
  3. 绑定Service出错时,KeyChain抛出AssertionError异常。
  4. AssertionError异常直接继承与Error类。方法调用者DevicePolicyManagerService并未捕获此异常。
  5. 因此,导致DevicePolicyManagerService执行出错,又该Service是系统核心服务运行于system_server进程,则该进程挂掉,重启。
  6. 如果打开隐式Intent验证机制,则抛出IllegalArgumentException,继承于RuntimeException,被DevicePolicyManagerService捕获,不会导致挂掉。系统继续往下运行。

解决

加一行catch即可,搞定。

        private void manageNotification(UserHandle userHandle) {
            ......
            // Call out to KeyChain to check for user-added CAs
            boolean hasCert = false;
            try {
                KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
                try {
                    if (!kcs.getService().getUserCaAliases().getList().isEmpty()) {
                        hasCert = true;
                    }
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
                } finally {
                    kcs.close();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (RuntimeException e) {
                Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
            } catch (AssertionError e) { <--- 万恶的异常。
                Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
            }
            ......
        }

存疑

个人认为,根本原因应该是IKeyChainService这个Service没有找到,导致了后续问题。待查到为什么找不到这玩意后再来更新。

补充

接上文,已经发现了为何IKeyChainService没有找到:
IKeyChainServicecom.android.keychain提供的服务,属于KeyChain这个MODEL。经查,发现是我们的ROM中没有加入此模块。与AOSP源码对比,更改build/target/product/core.mk,加入KeyChain即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值