Android Intents

Issue #11Android, April 2014

Introduction

Perhaps a very distinctive thing about Android is the ability for applications to launch other apps or easily share content. Back in the days of iOS 1.0, it quickly became obvious that applications couldn’t really talk to each other (at least non-Apple applications), even after the first iOS SDK was released.

Before iOS 6, attaching a photo or a video to an email you were already composing was definitely a chore. It was not until Apple added the ability in iOS 6 that this was really possible. Android, on the other hand, was designed to support this behavior since day one.

There are other simple examples where it really becomes clear how different both platforms behave. Imagine the following scenario: you take a picture and want to retouch it with some image editing app, and later share it on Instagram.

Please note: this is just a example to illustrate a point.

This is how you do it on iOS:

  1. Open Camera App, and take the picture.
  2. Go to the Home Screen, find yourEditPhotoapp, launch it, open existing photo, find it in the Camera Roll, make your edits.
  3. IfEditPhotosupports sharingandInstagram is on the list, you’re good to go!
  4. Otherwise, you will have to Save the image in the Photo Library.
  5. Go to the Home Screen again, findInstagram, launch it…
  6. Import the recently saved photo, and then share it on Instagram with your hipster friends. ;)

On Android, things are a lot easier:

  1. Open Camera App, and take the picture.
  2. Swipe Right to see the ‘Gallery,’ and click the Share button. Pick yourEditPhotoapp and make your edits.
  3. IfEditPhotosupports sharing (I haven’t seen a photo editing app that doesn’t), tap it and select Instagram. If it doesn’t, removeEditPhotoapp and get a decent photo editor or use the built-in editor, which has gotten really good in KitKat.

Notice that if iOS apps support sharing between them, the flow is similar. The biggest difference is that if the app is not supported, you just can’t do it directly. Instagram is an easy and popular one, just like Facebook or Twitter, but there are dozens of other not-so-supported apps out there.

Let’s say you have a picture in Instagram and you want to share it to Path (I know, not a lot of people use Path, but still…). In Android, you would likely find Path in thechooser dialog. As simple as that.

Let’s get back on topic.Intents.

What is an Android Intent?

The English dictionary defines an Intent as:

nounintention or purpose

According to the official Androiddocumentation, anIntentis a messaging object you can use to request an action from another app component. In truth, an Intent is an abstract description of an operation to be performed.

This sounds interesting, but there’s more than meets the eye. Intents are used everywhere, no matter how simple your app is; even your Hello World app will use an Intent. That’s because the most common case for an Intent is to start anActivity.1

Activities and Fragments, What are You Talking About?

The closest thing to anActivityin iOS would be aUIViewController. Don’t go around looking for an Android equivalent of anApplicationDelegate; there is none. Perhaps the closest thing would be theApplicationclass in Android, but there are a lot architecture differences between them.

As screens in devices grew bigger, the Android team added the concept ofFragments.2The typical example is the News Reader app. On a phone with a small screen, you only see the list of articles. When the user selects one, the article opens in fullscreen.

BeforeFragments, you would have had to create two activities (one for the list, and one for the fullscreen article) and switch between them.

This worked well, until tablets with big screens came. Since you can only haveoneactivity visible at a time (by design), the Android team invented the concept ofFragments, where a hostingActivitycan display more than oneFragmentat the same time.

Now, instead of having two differentActivities, you can have one that will displaytwoFragments– one for the list of articles and one that is capable of showing the selected article fullscreen. In phones or devices with small screens, you would simply swap theFragmentwhen the user selected an article, but on tablets, the same activity would host both at the same time. To visualize this, think of the Mail app on an iPad, where you see the inbox on the left and the mail list on the right.

Starting Activities

Intents are commonly used to start activities (and to pass data between them). AnIntentwill glue the two activities by defining an operation to be performed: launch anActivity.

Since starting anActivityis not a simple thing, Android has a system component calledActivityManagerthat is responsible for creating, destroying, and managing activities. I won’t go into much more detail about theActivityManager, but it’s important to understand that it keeps track of all the open activities and delivers broadcasts across the system; for example, it notifies the rest of the Android system once the booting process is finished.

It’s an important piece of the Android system and it relies onIntentsto do much of its work.

So how does Android use anIntentto start anActivity?

If you dig through theActivityclass hierarchy, you will find that it extends from aContext, which, in turn, contains a method calledstartActivity(), defined as:

public abstract void startActivity(Intent intent, Bundle options);

This abstract method is implemented inActivity. This means you can start activities from any activity, but you need to pass anIntentto do so. How?

Let’s imagine we want to launch anActivitycalledImageActivity.

TheIntentconstructor is defined as:

public Intent(Context packageContext, Class<?> cls)

So we need aContext(remember, anyActivityis a validContext) and aClasstype.

With that in mind:

Intent i = new Intent(this, ImageActivity.class);startActivity(i);

This triggers a lot of code behind the scenes, but the end result is that if everything went well, yourActivitywill start its lifecycle and the current one will likely be paused and stopped.

Since Intents can also be used to pass certain data between activities, we could use them to passExtras. For example:

Intent i = new Intent(this, ImageActivity.class);i.putExtra("A_BOOLEAN_EXTRA", true); //boolean extrai.putExtra("AN_INTEGER_EXTRA", 3); //integer extrai.putExtra("A_STRING_EXTRA", "three"); //string extrastartActivity(i);

Behind the scenes, theextrasare stored in an AndroidBundle,3which is pretty much a glorified serializable container.

The nice thing is that ourImageActivitywill receive these values in theIntentand can easily do:

 int value = getIntent().getIntExtra("AN_INTEGER_EXTRA", 0); //name, default value

This is how you pass data between activities. If you can serialize it, you can pass it.

Imagine you have an object that implementsSerializable. You could then do this:

YourComplexObject obj = new YourComplexObject();Intent i = new Intent(this, ImageActivity.class);i.putSerializable("SOME_FANCY_NAME", obj); //using the serializable constructor herestartActivity(i);

And it would work the same way on the otherActivity:

YourComplexObject obj = (YourComplexObject) getIntent().getSerializableExtra("SOME_FANCY_NAME");

As a side note,always check for null when retrieving the Intent:

if (getIntent() != null ) {         // you have an intent, so go ahead and get the extras…}

This is Java, and Java doesn’t like null references. Get used to it. ;)

When you start an activity with this method (startActivity()), your current activity is paused, stopped (in that order) and put in the task stack, so if the user presses thebackbutton, it can be restored. This is usually OK, but there are certainFlagsyou can pass to the Intent to indicate theActivityManagerthat you’d like to change this behavior.

Although I will not go into detail because it’s a rather extensive subject, you should take a look at theTasks and Back Stack official docsto understand what elseIntent Flagscan do for you.

So far, we’ve only used Intents to open other activities in our application, but what else can anIntentdo?

There are two more things that are possible thanks toIntents:

  • Start (or send a command to) aService.4
  • Deliver aBroadcast.

Starting a Service

SinceActivitiescannot be put in the background (because they would be paused, stopped, and maybe destroyed), the alternative – if you need to run a background process while there’s no visible UI – is to use aService. Services are also a big subject, but the short version is they can perform tasks in the background, regardless of whether or not the UI is visible.

They are prone to be destroyed if memory is needed and they run on the UI thread, so any long-time running operation should spawn a thread, usually through anAsyncTask. If aServiceneeds to do something like media playback, it can request aForegroundstatus, whichforcesthe application to show a permanent notification in the Notification Bar to indicate to the user that something is happening in the background. The app can cancel the foreground status (and therefore dismiss the notification), but by doing so, theServiceloses its higher-priority status.

Servicesare very powerful mechanisms that allow Android applications to perform the ‘real multitasking’ that so controversially affected battery life in the past. Back when iOS had virtually no multitasking, Android was already dancing with the stars. When correctly used,Servicesare an integral part of the platform.

In the past, the biggest problem was that there were ways to request aServiceforeground statuswithoutshowing a notification. This behavior was abused by developers who left tasks running in the background without the user knowing about it. In Android 4.0 (Ice Cream Sandwich), Google finally fixed the ‘hidden’ notification, and now if your app is doing something in the background, the userwill seethe notification alongside your app’s name and icon. You can even access the application information directly from the notification bar (and kill it!). Yes, Android’s battery life is nowhere near as good as with iOS, but it’s no longer because of hiddenServices. ;)

How areIntentsandServicesrelated?

In order to start a service, you need to use anIntent. Once aServiceis started, you can keep sending commands to the service, until it’s stopped (in which case it will restart).

The easiest way to understand it is to see some code:

In someActivity, you could do:

Intent i = new Intent(this, YourService.class);i.setAction("SOME_COMMAND");startService(i);

What happens next will depend on whether or not this was the first time you did that. If so, the service will be started (it’s constructor, andonCreate()methods will be executed first). If it was already running, theonStartCommand()method will be directly called.

The signature is:public int onStartCommand(Intent intent, int flags, int startId);

Let’s ignore theflagsandstartId, as they have nothing to do with the topic at hand, and concentrate on theIntent.

We set anActionearlier withsetAction("SOME_COMMAND"). This action is passed to theServiceand we can retrieve it from theonStartCommand(). For example, in ourService, we could do:

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {    String action = intent.getAction();    if (action.equals("SOME_COMMAND")) {        // Do SOME COMMAND ;)    }    return START_NOT_STICKY; // Don't restart the Service if it's killed.}

If you are wondering what thatSTART_NOT_STICKYthing is, theAndroid docsare an excellent source of information.

TL;DR:if thisServicegets killed, don’t attempt to restart it. The opposite isSTART_STICKY, which means restart theServiceshould its process die.

As you can see from the snippet above, you can retrieve theActionfrom theIntent. This is how you usually communicate withServices.

Let’s imagine we are developing an application that can reproduce YouTube videos and stream them to a Chromecast (the stock YouTube app already does this, but this is Android, so we want to make our own).

The streaming would be implemented in aServiceso the streaming doesn’t stop if the user goes to another application while he or she is playing a video. You could have different actions defined, like:

ACTION_PLAY, ACTION_PAUSE, ACTION_SKIP.

You could also have aswitchorifstatement in theonStartCommand()to deal with each case.

The names can be anything you want, but you will usually want to use constants (as we will see later) and better names to avoid conflicts with other apps, usually full package names like: ’com.yourapp.somepackage.yourservice.SOME_ACTION_NAME’. This can also be made private if you only want your own app to be able to communicate with your service, but it can be public, meaning you could let other apps use yourService.

Sending and Receiving Broadcasts

Part of the strength of the Android platform is that any application can broadcast anIntentand anyone can define aBroadcastReceiverto receive one. In fact, Android itself makes use of this mechanism to inform apps and the system about events. For example, if the network goes down, an Android component will broadcast anIntent. If you were interested in this, you could create aBroadcastReceiverwith the rightfilterto intercept that and act accordingly.

Think of this as a global channel you can subscribe to, add the filters you care for, and receive notifications when those broadcasts occur. You can define them privately if you want, meaning only your app will be able to receive them.

To continue with the previous example of our YouTube streaming service, if there were a problem with video playback, the service couldbroadcastanIntentsaying, “Hey, there was a problem and I will now stop playback.”

Your application could register aBroadcastReceiverto listen to yourServiceso it can react to that.

Let’s see some code to illustrate.

You have anActivitythat is displaying the currently playing music track alongside with the media buttons (play, pause, stop, etc.). You are interested in knowing what your service is doing; if there’s an error, you want to know (so you can show an error message, etc.).

In your activity (or in its own .java file) you would create your broadcast receiver:

private final class ServiceReceiver extends BroadcastReceiver {    public IntentFilter intentFilter;    public ServiceReceiver() {        super();        intentFilter = new IntentFilter();        intentFilter.addAction("ACTION_PLAY");        intentFilter.addAction("ACTION_STOP");        intentFilter.addAction("ACTION_ERROR");    }    @Override    public void onReceive(final Context context, final Intent intent) {        if (intent.getAction().equals("ACTION_ERROR")) {           // THERE HAS BEEN AN ERROR, PLAYBACK HAS STOPPED        } else if (intent.getAction().equals("ACTION_PLAY")){           // Playback has started        }        // etc…    } }

That’s your basic receiver. Notice how we added anIntentFilterwith theActionsthat we’re interested in. We called themACTION_PLAY,ACTION_STOP, andACTION_ERROR.

Since we use Java and Android has some conventions, we’d call this:

private ServiceReceiver mServiceReceiver;as a fieldmemberof ourActivity. In ouronCreate()method we instantiate it with:mServiceReceiver = new ServiceReceiver();

But creating this object is not enough. We also need to register it somewhere. Initially, you may think that a good place to do it would be theonStart()method of ourActivity. When theonStart()method is executed, that means ourActivityis visible to the user.

The signature for the method is (inContext):

public abstract Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);

ActivitiesandServicesare alsoContexts, so both implement this method. This means that either can register one or moreBroadcastReceivers.

The method needs aBroadcastReceiverand anIntentFilter. We’ve created both, so we pass them:

@Overridepublic void onStart() {    onStart();      registerReceiver(mServiceReceiver, mServiceReceiver.intentFilter);}

In order to be good Java/Android citizens, we want to unregister if ourActivityis stopping:

@Overridepublic void onStop() {    super.onStop();    unregisterReceiver(mServiceReceiver);}

This approach is not incorrect, but you have to keep in mind that if the user navigates outside of your application, you will never receive the broadcast. This is because yourActivitywill be stopped, and because you are unregistering duringonStop(). When designingBroadcastReceivers, you have to keep in mind whether or not this makes sense. There are other ways to implement them (outside anActivity) to act as independent objects.

When theServicedetects an error, it can dispatch a broadcast that ourBroadcastReceiverwill receive in itsonReceive()method.

Broadcast receivers are very powerful mechanisms and are core mechanisms in Android.

Astute readers may be wondering howglobalthese broadcasts are and how to make them private or restricted to their own apps.

There are two types of Intents:explicitandimplicit.

The former will specify the component to start by the fully qualified name, something that you will always know for your own application. The latter declares a general action to perform, which allows a component from another app to handle it. And here is where things start to get interesting.

Let’s focus onimplicit Intents, since we have already seenexplicit Intentsin action with our example above.

The best way to see the power ofimplicit intentsis by using a simple example. There are two ways to use a filter. The first approach is more iOS friendly, because iOS can define a custom URI scheme, for example: yourapp://some.example.com

If you have to support the same URI from both iOS and Android, then this will be your only choice. On the other hand, if you are able to use a regular URL (http://your.domain.com/yourparams) then you should try to do it this way on Android. This raises the big argument of whether using a custom URI is good or bad, and I’m not going to dive into that at this point, suffice to say that (and I quote):

This goes against the web standards for URI schemes, which attempts to rigidly control those names for good reason – to avoid name conflicts between different entities. Once you put a link to your scheme on a web site, you have put that little name into the entire Internet’s namespace, and should be following those standards.

Source:StackOverflow

Arguments aside, let’s take a look at two examples, one for YouTube using a regular URL, and then define our own custom URI scheme for our own app.

It’s simpler than it looks because Android has a configuration file calledAndroidManifest.xml, where it stores metadata about yourActivities,Services,BroadcastReceivers, versions, Intent filters, and more. Every application has this file – you can read more about ithere.

The idea behind an Intent filter is that the system will check for installed apps to see if there’s one (or more) that can handle a particular URI.

If your app matches and it’s the only one, it will be automatically open. Otherwise, you will see a dialog like this:

So how did the official YouTube app end up in that list?

I tapped on a YouTube link in the Facebook App. How did Android know that it was YouTube?What kind of sorcery is this?

If we had access to YouTube’sAndroidManifest.xml, we would likely see something like this:

1 <activity android:name=".YouTubeActivity">2     <intent-filter>3        <action android:name="android.intent.action.VIEW" />4       <category android:name="android.intent.category.DEFAULT" />5         <category android:name="android.intent.category.BROWSABLE" />6       <data7        android:scheme="http"8        android:host="www.youtube.com"9        android:pathPrefix="/" />10   </intent-filter>11 </activity>

Let’s examine this simple XML line by line.

Line 1 declares the activity (you must declare eachActivityin Android, regardless of the Intent filters).

Line 3 declares the action. In this case,VIEWis the most common action, indicating that data will be displayed to the user. Some actions can only be sent by the system because they are protected.

Lines 4-5 declare the categories. Implicit Intents require at least one action and one category. Categories provide additional detail about the action the Intent performs. When resolving an Intent, only activities that provide all of the requested categories will be used.android.intent.category.DEFAULTis applied to everyActivityby Android when you usestartActivity(), so if you want your activity to receive implicit Intents, it must include it.

android.intent.category.BROWSABLEis a different beast:

Activities that can be safely invoked from a browser must support this category. For example, if the user is viewing a web page or an e-mail and clicks on a link in the text, the Intent generated execute that link will require the BROWSABLE category, so that only activities supporting this category will be considered as possible actions. By supporting this category, you are promising that there is nothing damaging (without user intervention) that can happen by invoking any matching Intent.

Source:Android Documentation

This is an interesting point, and this gives Android a very powerful mechanism for apps to respond to any link. You could create your own web browser and it will respond to any URL; the user could set it as default if he or she wishes.

Lines 6-9 declare the data to operate on. This is part of thetype. In this simple example, we’re filtering by scheme and host, so any http://www.youtube.com/ link will work, even if tapped on a WebBrowser.

By adding these lines to YouTube’sAndroidManifest.xml, when it’s time to perform anIntent resolution, Android performs a matching of an Intent against all of the<intent-filter>descriptions in the installed application packages (orBroadcastReceiversregistered via code, like our example above).

The AndroidPackageManager5will be queried using theIntentinformation (the action, type, and category), for a component that can handle it. If there’s one, it will be automatically invoked, otherwise the above dialog will be presented to the user, so he or she can chose (and maybe set as default) a particular app or package to handle the type of Intent.

This works well for many apps, but sometimes you need to use the same iOS link (where your only choice is to use a custom URI). In Android, you could support both, since you can add more filters to the same activity. To continue with the YouTubeActivity, let’s add now an imaginary YouTube URI scheme:

1 <activity android:name=".YouTubeActivity">2     <intent-filter>3        <action android:name="android.intent.action.VIEW" />4       <category android:name="android.intent.category.DEFAULT" />5         <category android:name="android.intent.category.BROWSABLE" />6       <data7        android:scheme="http"8        android:host="www.youtube.com"9        android:pathPrefix="/" />10      <data android:scheme="youtube" android:host="path" />11   </intent-filter>12 </activity>

The filter is almost the same, except we added a new line 10, specifying our own scheme.

The app can now open links like:youtube://path.to.video.and normal HTTP links. You can add as many filters and types to anActivityas you wish.

How Bad is it to Use my Custom URI Scheme?

The problem is that it doesn’t follow the standard rules for URIs defined by the W3C, at least according to purists. The truth is that this is not entirely true or a real problem. You are OK to use custom URI schemes, as long as you restrict them to your own internal packages. The biggest problem with a custom (public) URI scheme is name conflict. If I define amyapp://, nothing stops the next app from doing the same, and we have a problem. Domains, on the other hand, are never going to clash, unless I’m trying to create my own YouTube player, in which case, it’s fine for Android to give me the choice to use my own YouTube player or the official Android app.

Meanwhile, a custom URL likeyourapp://some.datamay not be understood by a web browser and it can lead to 404 errors. You’rebendingthe rules and standard conventions.

Sharing Data

Intentsare used when you have something you want tosharewith other apps, such as a post in a social network, sending a picture to an image editor, or sending an email, an SMS, or something via any other instant messaging service. So far, we have seen how to create intent filters and register our app to be notified when we are capable of handling certain types of data. In this final section, we’ll see how to tell Android that we have something toshare. Remember what anIntentis:an abstract description of an operation to be performed.

Posting to Social Networks

In the following example, we’re going to share a text and let the user make the final decision:

1  Intent shareIntent = new Intent(Intent.ACTION_SEND);2  shareIntent.setType("text/plain");3  shareIntent.putExtra(Intent.EXTRA_TEXT, "Super Awesome Text!");4  startActivity(Intent.createChooser(shareIntent, "Share this text using…"));

Line 1 creates anIntentand passes an action using the constructor:public Intent(String action);

ACTION_SENDis used when you want todeliver some data to someone else. In this case, the data is our “Super Awesome Text!” But we don’t know who that ‘someone else’ is yet. It will be up to the user to decide that.

Line 2 sets an explicit MIME data type oftext/plain.

Line 3 adds the data (the text) to this Intent using an extra.

Line 4 is where the magic happens.Intent.createChooseris a convenience function that wraps your original Intent in a new one with an action,ACTION_CHOOSER.

There’s no rocket science going on here. The action is designed so an activity chooser is displayed, allowing the user to pick what he or she wants before proceeding. Sometimes you want to be explicit (so if the user is sending an email, you may want to use the default email client directly), but in this case, we want the user to select any app to handle this text.

This is what I see when I use it (the list is longer – it’s a scrollable list):

I have decided to send it to Google Translate. Here’s the result:

The results attempting to do it in Google Translate speak in Italian.

An Extra Example

Before wrapping up, let’s see another example. This time, we’ll see how to share and receive an image. We want the app to appear in the chooser when the user shares an image.

We need to do something like this in ourAndroidManifest:

1    <activity android:name="ImageActivity">2        <intent-filter>3            <action android:name="android.intent.action.SEND"/>4            <category android:name="android.intent.category.DEFAULT"/>5            <data android:mimeType="image/*"/>6        </intent-filter>7    </activity>

Remember, we need at least one action and one category.

Line 3 sets the action asSEND, so we will matchSENDactions.

Line 4 declares theDEFAULTcategory. This category gets added by default when you usestartActivity().

Line 5 is they key that sets the MIME type asany type of image.

Now, in ourImageActivity, we handle the Intent like this:

1    @Override2    protected void onCreate(Bundle savedInstanceState) {3        super.onCreate(savedInstanceState);4        setContentView(R.layout.main);5        6        // Deal with the intent (if any)7        Intent intent = getIntent();8        if ( intent != null ) {9            if (intent.getType().indexOf("image/") != -1) {10                 Uri data = intent.getData();11                 // handle the image…12            } 13        }14    }

The relevant code is in line 9, where we’re actually checking if the Intent contains image data.

Now, let’s do the opposite. This is how wesharean image:

1    Uri imageUri = Uri.parse("/path/to/image.png");2    Intent intent = new Intent(Intent.ACTION_SEND);3    intent.setType("image/png");    4    intent.putExtra(Intent.EXTRA_STREAM, imageUri);5    startActivity(Intent.createChooser(intent , "Share"));

The interesting code is in line 3, where we define the MIME type (so onlyIntentFilterscapable of dealing with this type will be shown), and in line 4, where we actually place the data that will be shared.

Finally, line 5 creates thechooserdialog we’ve seen before, but only containing apps that can handleimage/png.

Summary

We have scratched the surface regarding what Intents can do and how information can be shared in Android, but there’s a lot more to see. It’s a very powerful mechanism and one aspect that makes Android users frown when they use iOS devices. They (myself included) find the process of always going home and/or using the Task Switcher in iOS very inefficient.

This doesn’t really mean Android is technically better or that the Android method is superior when it comes to sharing data between applications. In the end, everything is a matter of preference, just like thebackbutton some iOS users loathe when they grab an Android device. On the other hand, Android users love that button. It’s standard and efficient and it’s always in the same place, next to thehomebutton.

When I lived in Spain, I remember they had a very good saying: “Colors were created so we can all have different tastes” (or something like that). ;)

更多相关文章

  1. android中文api(89)――ViewManager
  2. SpannableString的使用方法
  3. Android中文API(125) ―― VideoView
  4. 用自定义 LayoutManager 实现 Android(安卓)中 Gallery 或者 Vie
  5. Error:Android(安卓)Source Generator: Cannot parse SDK Androi
  6. trinea博客地址
  7. android 图片高斯模糊
  8. How to Implement Push Notifications for Android
  9. android 图表引擎AChartEngine(柱状图)

随机推荐

  1. android评分条RatingBar自定义设置
  2. Android五大布局介绍&属性设置大全
  3. Android 开发艺术探索读书笔记 10 -- And
  4. 使用jQuery Mobile和Phone Gap开发Androi
  5. Android图形基础
  6. cocos2d-x3.2 在Windows下打包Android平
  7. Android的ImageView填充方式
  8. 通过命令行管理AVD
  9. pc 与 android webrtc 通信的研究
  10. 【移动生活】Google项目副总裁安迪·鲁宾