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进程报错,定位错误位于以上启动Service代码。
3.绑定Service出错时,KeyChain抛出AssertionError异常。
4.AssertionError异常直接继承与Error类。方法调用者DevicePolicyManagerService并未捕获此异常。
4.因此,导致DevicePolicyManagerService执行出错,又该Service是系统核心服务运行于System_Server进程,则System进程挂掉,重启。
5.如果打开隐式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没有找到,导致了后续问题。待查到为什么找不到这玩意后再来更新。

posted @ 2016-01-14 01:30  tong.zhao  阅读(1737)  评论(0)    收藏  举报