| page.title=WikiNotes: Routing Intents |
| parent.title=Articles |
| parent.link=../browser.html?tag=article |
| @jd:body |
| |
| |
| <p>In <a href="wikinotes-linkify.html">the Linkify! article</a>, we talked about |
| using Linkify to turn wiki words (those that match a regular expression that we |
| have defined) into a <code>content:</code> URI and defining a path to data that |
| matched a note belonging to that wiki word. As an example, a matching word like |
| <code>ToDoList</code> would be turned into a URI such as |
| <code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList |
| </code> and then acted upon using the VIEW action from the Linkify class.</p> |
| |
| <p>This article examines how the Android system takes this combination of |
| <code>VIEW</code> action and <code>content:</code> URI and finds the correct |
| activity to fire in order to do something with the data. It will also explain |
| how the other default links created by Linkify, such as web URLs and telephone |
| numbers, also result in the correct activity to handle that data type being |
| fired. Finally, this article will start to examine the custom |
| <code>ContentProvider</code> that has been created to handle WikiNotes data. The |
| full description of the ContentProvider and what it does will span a couple more |
| articles as well, because there is a lot to cover.</p> |
| |
| <h3>The Linkify-calls-intent Workflow</h3> |
| |
| <p>At a high level, the steps for Linkify to invoke an intent, and for the |
| resulting activity (if any) to handle it, look like this:</p> |
| |
| <ol> |
| <li>Linkify is invoked on a TextView to turn matching text patterns into Intent links.</li> |
| <li>Linkify takes over monitoring for those Intent links being selected by the user.</li> |
| <li>When the user selects a link, Linkify calls the VIEW action using the content: URI associated with the link.</li> |
| <li>Android takes the content: URI that represents the data, and looks for a |
| ContentProvider registered in the system that matches the URI.</li> |
| <li>If a match is found, Android queries the ContentProvider using the URI, |
| and asks what MIME type the data that will be returned from the URI is.</li> |
| <li>Android then looks for an activity registered in the system with an |
| intent-filter that matches both the VIEW action, and the MIME type for |
| the data represented by the content: URI.</li> |
| <li>Assuming a match is found, Linkify then invokes the intent for |
| the URI, at which point the activity takes over, and is handed |
| the content: URI.</li> |
| <li>The activity can then use the URI to retrieve the data and act on |
| it.</li> |
| </ol> |
| |
| <p>This is actually a simpler process than it |
| sounds, and it is quite lightweight as well. Perhaps a more |
| understandable statement about how it works might be:</p> |
| |
| <p>Linkify is used to turn matching text into hot-links. When the user |
| selects a hot-link, Android takes the data locator represented by the |
| hot-link and looks for a data handler for that data locator. If it |
| finds one, it asks for what type of data is returned for that locator. |
| It then looks for something registered with the system that handles |
| that type of data for the VIEW action, and starts it, including the |
| data locator in the request.</p> |
| |
| <p>The real key here is the MIME type. MIME stands for <a |
| href="http://en.wikipedia.org/wiki/MIME">Multipurpose Internet Mail |
| Extensions</a> — a standard for sending attachments over email. The MIME |
| type (which is the part Android uses) is a way of describing certain kinds of |
| data. That type is then used to look for an Activity that can do something with |
| that data type. In this way, ContentProviders and Activities (or other |
| IntentReceivers) are decoupled, meaning that a given Content URI might have a |
| different ContentProvider to handle it, but could still use the same MIME type |
| meaning that the same activity could be called upon to handle the resulting |
| data.</p> |
| |
| <h3>Linkify on a wiki word</h3> |
| |
| <p>Using the above workflow, let's take a look at exactly how the process |
| works in WikiNotes for Android:</p> |
| |
| <p>First, Linkify is used to turn text matching the wiki word regular expression |
| into a link that provides a Content URI for that wiki word, for example |
| <code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList</code>.</p> |
| |
| <p>When the user clicks on the wiki word link, Linkify invokes the VIEW |
| action on the Content URI. At this point, the Android system takes over |
| getting the Intent request to the correct activity.</p> |
| |
| <p>Next, Android looks for a ContentProvider that has been registered |
| with the system to handle URIs matching our Content URI format.</p> |
| |
| <p>In our case, we have a definition inside |
| <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">our application's AndroidManifest.xml</a> |
| file that reads:</p> |
| |
| <pre><provider name="com.google.android.wikinotes.db.WikiNotesProvider" |
| android:authorities="com.google.android.wikinotes.db.wikinotes" /></pre> |
| |
| <p>This establishes that we have a ContentProvider defined in our application |
| that provides the "root authority": |
| <code>com.google.android.wikinotes.db.wikinotes</code>. This is the first part |
| of the Content URI that we create for a wiki word link. Root Authority is just |
| another way of thinking about a descriptor that is registered with Android to |
| allow requests for certain URLs to be routed to the correct class.</p> |
| |
| <p>So, the whole definition is that a class called |
| <code>com.google.android.wikinotes.db.WikiNotesProvider</code> is registered |
| with the system as able to handle the |
| <code>com.google.android.wikinotes.db.wikinotes</code> root authority (i.e. URIs |
| starting with that identifier).</p> |
| |
| <p>From here, Android takes the rest of the URI and presents it to that |
| ContentProvider. If you look at the |
| <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider |
| class</a> and scroll to the very bottom, in the static block there, you can see |
| the pattern definitions to match the rest of the URL.</p> |
| |
| <p>In particular, take a look at the two lines:</p> |
| |
| <pre>URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes", NOTES); |
| URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes/*", NOTE_NAME);</pre> |
| |
| <p>These are the definitions of URIs that our ContentProvider recognizes and can |
| handle. The first recognizes a full URI of |
| <code>content://com.google.android.wikinotes.db.wikinotes/wikinotes</code> and |
| associates that with a constant called NOTES. This is used elsewhere in the |
| ContentProvider to provide a list of all of the wiki notes in the database when |
| the URI is requested.</p> |
| |
| <p>The second line uses a wildcard — '*' — to match a request of the |
| form that Linkify will create, e.g. |
| <code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList |
| </code>. In this example, the * matches the ToDoList part of the URI and is |
| available to the handler of the request, so that it can fish out the matching |
| note for ToDoList and return it as the data. This also associates that match |
| with a constant called NOTE_NAME, which again is used as an identifier elsewhere |
| in the ContentProvider.</p> |
| |
| <p>The other matches in this static block are related to forms of |
| searching that have been implemented in the WikiNotes for Android |
| application, and will be covered in later articles. Likewise, how the |
| data is obtained from this matching pattern will be the subject of the |
| next article.</p> |
| |
| <p>For right now we are concerned with the MIME type for the URI. This is |
| defined in the <code>getType()</code> method also in the |
| <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider |
| class</a> (about halfway through the file). Take a quick look at this. The key |
| parts for now are:</p> |
| |
| <pre>case NOTES: |
| return "vnd.android.cursor.<b>dir</b>/vnd.google.wikinote";</pre> |
| |
| <p>and</p> |
| |
| <pre>case NOTE_NAME: |
| return "vnd.android.cursor.<b>item</b>/vnd.google.wikinote";</pre> |
| |
| <p>These are the same constant names we defined in our pattern |
| matchers. In the first case, that of the all notes URI, the MIME type |
| returned is <code>vnd.android.cursor.dir/vnd.google.wikinote</code> |
| which is like saying an Android list (dir) of Google wiki notes (the |
| vnd bit is MIME-speak for "vendor specific definition"). Likewise, in |
| the case of a NOTE_NAME match, the MIME type returned is |
| <code>vnd.android.cursor.item/vnd.google.wikinote</code> which is |
| like saying an Android item of Google wiki notes.</p> |
| |
| <p>Note that if you define your own MIME data types like this, the |
| <code>vnd.android.cursor.dir</code> and <code>vnd.android.cursor.item</code> |
| categories should be retained, since they have meaning to the Android |
| system, but the actual item types should be changed to reflect your |
| particular data type.</p> |
| |
| <p>So far Android has been able to find a ContentProvider that handles |
| the Content URI supplied by the Linkify Intent call, and has queried |
| the ContentProvider to find out the MIME types for that URI. The final |
| step is to find an activity that can handle the VIEW action for that |
| MIME type. Take a look in the the |
| <a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">AndroidManifest.xml file</a> |
| again. Inside the WikiNotes activity definition, you will see:</p> |
| |
| <pre><intent-filter> |
| <action name="android.intent.action.VIEW"/> |
| <category name="android.intent.category.DEFAULT"/> |
| <category name="android.intent.category.BROWSABLE"/> |
| <data mimetype="vnd.android.cursor.item/vnd.google.wikinote"/> |
| </intent-filter></pre> |
| |
| <p>This is the correct combination of matches for the VIEW action on a |
| WikiNote type that is requested from the LINKIFY class. The DEFAULT |
| category indicates that the WikiNotes activity should be treated as a |
| default handler (a primary choice) for this kind of data, and the |
| BROWSABLE category means it can be invoked from a "browser", in this |
| case the marked-up Linkified text.</p> |
| |
| <p>Using this information, Android can match up the VIEW action request |
| for the WikiNotes data type with the WikiNotes activity, and can then |
| use the WikiNotes activity to handle the request.</p> |
| |
| <h3>Why do it like this?</h3> |
| |
| <p>It's quite a trip through the system, and there is a lot to absorb |
| here, but this is one of the main reasons I wanted to write WikiNotes |
| in the first place. If you follow and understand the steps here, you'll |
| have a good grasp of the whole Intents mechanism in Android, and how it |
| helps loosely coupled activities cooperate to get things done.</p> |
| |
| <p>In this case, we could have found another way to detect wiki words |
| based on a regular expression, and maybe written our own handler to |
| intercept clicks within the TextView and dig out the right data and |
| display it. This would seem to accomplish the same functionality just |
| as easily as using intents, so what is the advantage to using the full |
| Intents mechanism?</p> |
| |
| <p>In fact there are several advantages:</p> |
| |
| <p>The most obvious is that because we are using the standard Intent |
| based approach, we are not limited to just linking and navigating to |
| other wiki notes. We get similar behavior to a number of other data |
| types as well. For example, a telephone number or web URL in a wiki |
| note will be marked up by Linkify, and using this same mechanism (VIEW |
| action on the linked data type) the browser or dialer activities will |
| be automatically fired.</p> |
| |
| <p>It also means that each operation on a wiki note can be treated as a |
| separate life cycle by our activity. We are not dealing with swapping |
| data in and out of an existing activity - each activity works on a |
| particular wiki note and that's all you have to worry about.</p> |
| |
| <p>Another advantage is that we now have a public activity to handle |
| VIEW actions in WikiNotes no matter where the request comes from. |
| Another application could request to view a wiki note (perhaps without |
| even knowing what kind of data it is) and our activity could start up |
| and handle it.</p> |
| |
| <p>The backstack is automatically maintained for you too. As you |
| forward navigate through WikiNotes, Android maintains the history of |
| notes visited, and so when you hit the back button you go back to the |
| last note you were on. All this is free because we rely on the Android |
| intents mechanism.</p> |
| |
| <p>Finally, if you run WikiNotes for Android and then start DDMS to |
| take a look at the Activity threads in the WikiNotes application while |
| it is running, you can see that despite what you might think, letting |
| Android manage the navigation is very efficient. Create a few linked |
| notes, as many links deep as you like, and then follow them. If you |
| follow links hundreds of notes deep, you will still only see a handful |
| of WikiNotes activities. Android is managing the activities, closing |
| the older ones as necessary and using the life cycle to swap data in |
| and out.</p> |
| |
| <h3>Next Time</h3> |
| |
| <p>This was a long article, but necessarily so. It demonstrates the |
| importance of the Intents mechanism and to reinforce the notion that it |
| should be used whenever possible for forward navigation, even within a |
| single application. Illustrating this is one of the primary reasons I |
| wrote WikiNotes for Android in the first place.</p> |
| |
| <p>In the next article we will look deeper into the ContentProvider and |
| examine how it turns a Content URI into a row (or several rows) of data |
| that can be used by an activity.</p> |