Android 5.0之后隐式声明Intent 启动Service引发的问题

一.概述

Android系统升级到5.0之后做了不少的变化(5.0变化),开发人员一定要注意这些变化,要不然就有的折腾了.这次最大的变化应该是把Dalvik虚拟机改成了ART(Android Runtime),后续会专门讲解这一块.其他的都是一些零碎的问题,例如前段时间发了一篇Android
5.0之后修改了HashMap的实现(传送门).这篇主要讲一下遇到跟Service相关的问题.

二.详情

Service身为Android四大组件之一,它的使用频率还是比较高的,并且现在主要都是运用在比较关键的部位,例如升级推送等.在Android 5.0之后google出于安全的角度禁止了隐式声明Intent来启动Service.也禁止使用Intent filter.否则就会抛个异常出来.

官方的解释如下.

那么google到底是怎么限制的呢?限制的判定条件是什么呢?这些都可以从Android 4.4和Android 5.0的源码中找到区别.

在Android 4.4的ContextImpl源码中,能看到如果启动service的intent的component和package都为空并且版本大于KITKAT的时候只是报出一个警报,告诉开发者隐式声明intent去启动Service是不安全的.再往下看,丫的异常都写好了只是注释掉了,看来google早就想这么干了.

    private void validateServiceIntent(Intent service) {
        if (service.getComponent() == null && service.getPackage() == null) {
            if (true || getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.KITKAT) {
                Log.w(TAG, "Implicit intents with startService are not safe: " + service
                        + " " + Debug.getCallers(2, 3));
                //IllegalArgumentException ex = new IllegalArgumentException(
                //        "Service Intent must be explicit: " + service);
                //Log.e(TAG, "This will become an error", ex);
                //throw ex;
            }
        }
    }

果然在Android 5.0的源码中上面注释的代码已经不注释了,当targetSdkVersion版本大于LOLLIPOP直接异常抛出来,要求Service intent必须显式声明.所以如果开发的应用指定targetSdkVersion版本是小于LOLLIPOP的话还是按以前的方式给报个警报,这也就造成了如果没有做了完善的Android
5.0兼容就贸然把targetSdkVersion升到LOLLIPOP的话很有可能就会碰到这个问题.并且这个问题是很严重的,想象一下,你的app自升级的Service是隐式启动的,碰到这个问题后app就不能自升级了,这批用户有可能就一直停留在当前版本.会产生很致命的问题.

    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));
            }
        }
    }

从源码中的逻辑来看的话,判断一个intent是不是显式声明的点就是component和package,只要这两个有一个生效就不算是隐式声明的,接下来继续分析一下Intent的源码,可以看到下面三种构造方式,设置action来声明Intent是没有构建component的,所以显式声明需要用到第一和第二种构造(还有带packagename或component的拷贝构造),或者后面设置package属性.

    public Intent(Context packageContext, Class<?> cls) {
        mComponent = new ComponentName(packageContext, cls);
    }
    public Intent(String action) {
        setAction(action);
    }
    public Intent(String action, Uri uri,
                  Context packageContext, Class<?> cls) {
        setAction(action);
        mData = uri;
        mComponent = new ComponentName(packageContext, cls);
    }
    public Intent setPackage(String packageName) {
        if (packageName != null && mSelector != null) {
            throw new IllegalArgumentException(
                    "Can't set package name when selector is already set");
        }
        mPackage = packageName;
        return this;
    }

三.案例分析

经过这么分析之后单看这个问题是很好做兼容的,但是结合实际的一些复杂情况就不一定了.之前就碰到过一个比较复杂的兼容.

场景复现:

将项目里的targetSdkVersion升级到22之后,针对这篇Service的问题做了兼容,so easy啊,但是万万没想到项目里用的某盟的第三方登陆分享的SDK版本太老了(一年前),开发没有去升级SDK跟check兼容性.并且经过四轮测试愣是没有测到这个点,结果在线上发现了这个Bug,泥煤啊Android 5.0设备用微博登陆就闪退,泥煤啊.幸好这个项目做了线上bug热修复,这个时候就体现出这个功能的好处了,之前又博客简单讲解了一下实现原理(传送门).

接下来思路就是升级最新的某盟SDK然后打个补丁包给线上升级过去.然而升级完最新的SDK之后发现并没有什么卵用,看过热修复那篇博客的童鞋应该可以想到,那种打dex补丁的形式是不能添加新资源的,而最新的SDK新增了不少资源进来,所以这个思路是行不通的.

既然升级最新的SDK不行,那能不能修改老的SDK自己给它做Android 5.0兼容呢?理论上来说是可行的.说干就干,先记录一下SDK崩溃的记录.

再把SDK jar反编译一下,里面很多被混淆过,幸好崩溃的位置这个类的逻辑还有可读性都还比较完整.直接定位到崩溃的方法.果然是隐式绑定的Service,上面分析源码的时候从Intent构造源码可以看到这种声明方式既没有构造有效的component,也没有提供出packageName,崩了也无话可说.

既然定位到了位置,那么就单独把这个类抽离出来,新开一个Android工程,建立一个library的module,里面只有接下来要修改的这一个文件,由于这个类里面会引用SDK jar包里面的资源,所以也要把jar包引入到module里面.

包名一定要和之前一模一样.因为我们修改过之后要替换掉SDK中原有的.class文件.

build一下每问题,OK.因为这里启动的Service是新浪微博客户端里面的一个Service,连接上之后会通过AIDL进行跨进程通讯.所以我们没办法在构造Intent的时候就显式声明.既然没有办法构建有效的component,那么给它设置一个包名也可以生效的.既然是新浪微博那么包名应该是com.sina.weibo.

    private boolean a(Activity paramActivity)
    {
        Context localContext = paramActivity.getApplicationContext();
        Intent mIntent = new Intent("com.sina.weibo.remotessoservice");
        mIntent.setPackage("com.sina.weibo");
        return localContext.bindService(mIntent, this.serviceConnection, Context.BIND_AUTO_CREATE);
    }

reBuild一下,然后去主项目build文件夹里面的中间资源里面找到module打成的jar包,直接把编译好的.class解压出来.

解压某盟的SDK,进入到正确的路径下直接替换刚才编译出来的.class文件.

回到SDK的跟目录下,运行 jar cvf sdk.jar * 命令进行重打包,将重新打好的SDK替换到项目里面.验证bug.BinGo!搞定!这样就可以打个dex补丁包给线上的版本更新过去修复5.0兼容的bug了.

四.总结

这里结合这个案例简单分析了一下Android 5.0之后对启动绑定Service做的变化.碰到这种问题也是因为做兼容的时候没有覆盖所有的地方,这些坑都是跑不掉的.

转载请注明出处:http://blog.csdn.net/l2show/article/details/47421961

时间: 2024-10-28 17:05:41

Android 5.0之后隐式声明Intent 启动Service引发的问题的相关文章

Android中显示和隐式Intent的使用

显示启动activity                                                                                                                   这个很简单,下面是代码示例:为了看着方便,所以就把layout直接截图了,里面的代码只是一些简单的布局.activity_login.xml: activity_main.xml: 下面是Manifest.xml中的代码 1 <?xml vers

隐式调用 Intent 大全

//调用浏览器 Uri uri = Uri.parse(""); Intent it = new Intent(Intent.ACTION_VIEW,uri); startActivity(it); //显示某个坐标在地图上 Uri uri = Uri.parse("geo:38.899533,-77.036476"); Intent it = new Intent(Intent.Action_VIEW,uri); startActivity(it); //显示路径

警告:隐式声明与内建函数&#39;exit&#39;不兼容解决方案

警告:隐式声明与内建函数'exit'不兼容解决方案 [cpp] view plaincopy #include <stdio.h> int main() { printf("hello world!/n"); exit(0); } 原因其实很简单了,没有把stdlib.h包含进来,加上就没问题了.后来google搜索的时候发现网上很多人有这个问题,今天来记下这个问题的解决方案. 以此类推,编译后出现警告:隐式声明与内建函数’XXX’不兼容的问题所在都是因为没有包含相应的头文

c语言函数的隐式声明

c语言里变量必须先声明后使用,函数也不例外,这点和js,php不一样. double function(void){ return 100.0; } 定义一个函数第一行,声明了函数的名字,参数类型个数,返回值,这称为函数原型, 函数原型也可单独写,不带函数体 double function(void); 编译器只有碰到函数原型的时候才知道这个函数的名字,参数类型个数返回值,到函数调用的时候才知道如何生成指令,所以函数原型必须出现在函数调用之前. 以下两段代码都能得到正确的结果. #include

Android 显示意图和隐式意图的区别

意图在android的应用开发中是很重要的,明白了意图的作用和使用后,对开发会有很大帮助.如果没有把意图搞懂,以后开发应用会感觉缺些什么. 意图的作用: 1.激活组件 2.携带数据 3.意图的匹配(运用到隐式意图) android基本的设计理念是鼓励减少组件间的耦合,因此android提供了Intent(意图),用意图激活其他组件.Intent提供了一种通用的消息系统,它允许在你的应用程序与其他应用程序间传递Intent来执行和产生事件.使用Intent可以激活android应用的三个核心组件:

gcc函数隐式声明

最近看代码看到一些代码中直接使用了一些函数,但在编译的时候却没有任何错误,非常奇怪.在C语言中不是所有函数在使用之前都应该被定义或声明吗?查查资料发现原来gcc在碰到这种情况的时候会隐式为我们声明一下使用的函数,并且默认情况下,并不会触发警告.比如我们使用了一个未声明的函数hello,gcc会为我们声明一个int hello().如果加上-Wimplicit选项的话,我们就可以看见隐式声明的警告了:bootmain.c:6:2: warning: implicit declaration of

var与Javascript变量隐式声明

在JavaScript中,var用来声明变量,但是这个语法并不严格要求,很多时修改,我们可以直接使用一个变量而不用var声明它.var x = "XX"; y ="xxx"; 诸如此类.这有一个问题,比如说在代码中的某一行,我想使用的一个已声明的变量x,结果由于打字或者拼写错误,这个变量被写成y了,结果相当于"隐式"声明了一个变量y,在实际编程过程中,这种错误有时比较难以发现. 除此之外,今天通过同事介绍,了解到这种"隐式声明"

警告: 隐式声明与内建函数‘fabs’不兼容 [默认启用]

在linux下编程,经常出现警告:隐式声明与内建函数'XXX'不兼容 [默认启用],这类问题都是因为没有包含相应的头文件,加上就可以了. 出现警告"隐式声明与内建函数'fabs'不兼容 "时,加上头文件math.h即可.

隐式意图 Intent

/显式意图 :必须指定要激活的组件的完整包名和类名 (应用程序之间耦合在一起) // 一般激活自己应用的组件的时候 采用显示意图 //隐式意图: 只需要指定要动作和数据就可以 ( 好处应用程序之间没有耦合) //激活别人写的应用 隐式意图, 不需要关心对方的包名和类名 1.Manifest <activity android:name="com.itheima.intent2.SecondActivity" > <intent-filter> <actio