说起 intent,我觉得我们应该先说说Android进程间的通信,进程与线程的区别,我就不啰唣了,一般情况下,一个应用是一个进程,如果在 AndroidManifest 里指定了 process 那么就会有多个进程,你看,大 Android 要实现多进程多么容易啊,多进程的好处是能分配到更多的内存,且进程间互不影响,比如A进程崩溃并不会导致整个App崩溃,主进程可能还在跑着。
正如前面所言,要说Android 的 intent,那么必须先说进程间通信,进程内,我相信没有什么比 LocalBroadcastManager 更方便了,它的优势很明显,只在该进程,注意不是该App,很多博客可能说应用内,其实是进程内,比如一个APP,A activity 和 b activity在同一个进程里,那么是可以互相收到的,否则就用 Broadcast 吧,但是广播也是有缺陷的,这种缺陷在于你不知道那些地方注册了监听,维护比较困难哦,但你不得不承认本地广播在进程内通信的重要性,一方便它比广播更高效,另一方面,只能进程内收到更安全,试想,如果我的应用注册了另一个app的广播,那么,我的应用便能监听它的应用事件。
总结起来,Android 进程内通信 是本地广播,而进程间通信是 广播,当然也可以用 contentprovider 以及 AIDL,但是广播更简单,而广播我们知道,就是发送一个 intent,那么intent 到底是什么?
在讨论这个问题之前,我们先看看 intent 能做什么,或者那些场景下会用到 intent
我们知道,Android 四大组件就是 activity、service、broadcast receiver、content provider,那么,我看来看看启动一个activity、service、或者发送广播,我们怎么做?我们很清楚,启动activity 是 startActivity,而启动 service 是 startService,而参数就是一个 intent,至于广播,则是 发送一个 intent,至于 content provider 则完全依赖 schema, 而 schema 同样和 intent 有着很深的渊源。
说起 schema 我们必须先说 intent-filter,你可以理解为意图过滤器,请看下面的例子:
<intent-filter> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.MAIN" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="lzh" /> </intent-filter>
这里,我们指定了scheme 为 lzh,意味着在浏览器打开 lzh://host:port/path?query 这种类型的 url 时是可以呼起这个应用的,至于这背后的具体实现,不是我们今天讨论的话题了。我说了在浏览器里输入可以打开这个应用,那么,如果我要用 intent 启动它呢?
我说过,启动activity有两种方式,显式和隐式,显式就是指明了具体的类,当然这也是我们平时用的最多的。那么隐式呢,我们接着上面的话题,来考虑如何在应用内隐式的启动这个activity,在应用内,我们可以通过下面简单的代码来实现:
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("lzh://goods:8080/goodsDetail?goodsId=100100")); startActivity(intent);
感觉似曾相识,对吧,那我们继续看一些例子吧。我们考虑下如何调用系统图库选择一张照图,很简单,不是吗?
Intent mIntent = new Intent(Intent.ACTION_PICK, null); mIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(mIntent, IMAGE_PICK);
这样就可以,但是,如果我的系统没有图库呢,作死的 root 了设备并删除了图库?那我们只能这样子了:
try { Intent mIntent = new Intent(Intent.ACTION_PICK, null); mIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(mIntent, IMAGE_PICK); } catch (Exception e) { e.printStackTrace(); ToastUtil.shortToast(R.string.image_pick_failed); }
崩溃谁不会解决啊,加个 try catch 总会吧,修复这类 bug 我最拿手了,哈哈,但是,等等,如果我手机安装了多个图库呢,你得让我选择,不能直接用那个默认的,好吧,如下:
try { Intent mIntent = new Intent(Intent.ACTION_PICK, null); mIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); Intent chooserIntent = Intent.createChooser(mIntent, "请选择您需要打开的软件"); startActivityForResult(chooserIntent, IMAGE_PICK); } catch (Exception e) { e.printStackTrace(); ToastUtil.shortToast(R.string.image_pick_failed); }
这样子总可以了吧,我用一个 intentChooser 总可以了吧,但是等等,如果我现在手机没有图库了呢,我又给全删了,那么,弹出的 dialog 会 显示 no application can perform this action 且不会崩溃,好吧,我们还是不用 try catch 了这货靠不住,简单粗暴,未必靠谱。我们这样子吧:
Intent mIntent = new Intent(Intent.ACTION_PICK, null); mIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); List<ResolveInfo> allMatches = mActivity.getPackageManager().queryIntentActivities(mIntent, PackageManager.MATCH_DEFAULT_ONLY); if (allMatches != null && allMatches.size() > 0) { Intent chooserIntent = Intent.createChooser(mIntent, "请选择您需要打开的软件"); startActivityForResult(chooserIntent, IMAGE_PICK); }else{ ToastUtil.shortToast(R.string.image_pick_failed); }
这个办法总可以了吧,还记得微信弹出的选择浏览器的窗口吗?我严重怀疑它用的也是这个方法,不过它不是同样的代码,他应该是获取到符合的应用后,再获取相关的名字,最后自己显示个dialog展示给你,你选择后,人家再给你通过隐式启动来完成,由于dialog是自己的,我可以把自己家的qq浏览器啊啥的都给你加进来,主人,你看我给你找到了你可能需要的软件,顺便把我妹妹拉来给你认识下哦,它可能是你更好的选择…当然,如果我们没有类似与微信这种特殊的需求,我们只是展示个系统的选择窗口,我们不扰民啊,那么,没必要获取整个列表然后判断size是否大于0,我们就看看有没有符合的就行。
Intent mIntent = new Intent(Intent.ACTION_PICK, null); mIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); if (mIntent.resolveActivity(mActivity.getPackageManager()) == null) { ToastUtil.shortToast(R.string.image_pick_failed); return; } Intent chooserIntent = Intent.createChooser(mIntent, "请选择您需要打开的软件"); startActivityForResult(chooserIntent, IMAGE_PICK);
没错,就这样子。到此你应该掌握了 Android 隐式启动 activity的最靠谱的办法,是的,就是上面这串代码,我们知道啊,这里用的可是 startActivityForResult 而不是 startActivity 啊,什么意思呢,毕竟多了一个 forResult 吗,我的 result 呢,这时候 onActivityResult 登场了,请看:protected void onActivityResult(int requestCode, int resultCode, Intent data) 这里,最终返回给我们的数据还是在 intent 里面,怎么玩也绕不开一个 intent
隐式启动暂时告一段落,我们看看上面那个
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("lzh://goods:8080/goodsDetail?goodsId=100100")); startActivity(intent);
这样子我们启动后,假设跳转到商详页面,那么这个页面如何获取参数呢,依旧和 intent 绕不开。
Uri uri = getIntent().getData(); if (uri == null) { return; } String scheme = uri.getScheme(); String host = uri.getHost(); int port = uri.getPort(); String path = uri.getPath(); String query = uri.getQuery(); String goodsId = uri.getQueryParameter("goodsId");
回过头来,我告诉你 intent 是什么,intent 翻译过来是 意图,说的不好听呢,可以是企图嘛,你整天看着对面的美女设计几个 intent 啊,哈哈哈。说的通俗点,你想干点啥啊,我觉得 intent 就是这个意思,Activity A 通过 startActivity 传递一个 intent 后 启动了 Activity B,而 B 里面还可以拿到 A 传递过来的信息,所以 Intent 是他们间的通信枢纽?而系统怎么知道是启动 Activity B 呢?你让我给你递情书,我怎么知道你要递给美女设计而不是那个测试呢?我们知道 startActivity 需要传递 一个 intent 参数,而且这个 intent 里 必须指定 要启动的 Activity,就像你要告诉我递给谁一样。比如这样子:startActivity(new Intent(this, BeautifulDesignGirl.class));
虽然还是含蓄了点,好吧,再清楚点吧:
Intent intent = new Intent(); ComponentName component = new ComponentName(this, BeautifulDesignGirl.class); intent.setComponent(component); startActivity(intent);
这样子清爽多了,看到了没,ComponentName,组件名称,如果你要顺利的启动 美丽的设计女孩这个Activity,你就需要指定这个组件的名称,intent 的第一个属性,组件。而前面,我们写过这样一段代码:Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("lzh://goods:8080/goodsDetail?goodsId=100100"));
Intent.ACTION_VIEW 没错,intent 的 第二个属性,action,用于指明你要干啥,是看一下呢还是?别想多了,不是你想干啥都可以的,intent-filter 的 action 节点指明了该组件能接受的 action,人家可是卖艺不卖身,除了 action,还有 category ,以及 type、data 都是在 intent-filter 里指定的。他们明确了一个 intent 的类型,数据类型以及数据。
首先 intent 由这么几部分组成,组件、action、category、type、data,另外还有 extras 、 flags,后面两者 篇幅所限不做讨论,组件指明了这个 intent 要启动的 activity,是美女设计还是测试?而action、category、type、data 则常见于 隐式启动,用于匹配指定的 组件。比如你需要一个18岁的小美女做女友,那么这样子的条件有一大堆人都能满足,接着你指定了 action,这个action 可能是你浏览网页或者拨打电话,然后过滤掉了一批人,接着你又来一个 category ,比如是桌面应用,或者是浏览器,接着又是一个 type ,比如是图片还是音乐等,最后是 data ,比如 刚开始的那段配置文件里的 data 指定了 schema。
intent 主要用于进程间通信,可能是同一个进程,也可能不是,启动 activity、service,一定要说 intent 是什么,我只能说是一个载体,包含了不少信息,这些信息可用于启动 activity 或者 service,另外还可以用于通信,通信则主要是 startActivityForResult 和 setResult 另外便是广播了。其实 启动 activity 和 service的时候本身可以携带数据,也可以理解为数据传递。