blob: 78fe62e101d10356c6577a935d661db51800b861 [file] [log] [blame]
Dirk Dougherty22558d02009-12-10 16:25:06 -08001page.title=WikiNotes: Routing Intents
Scott Main796ce772011-02-16 10:04:45 -08002parent.title=Articles
3parent.link=../browser.html?tag=article
Dirk Dougherty22558d02009-12-10 16:25:06 -08004@jd:body
5
6
7<p>In <a href="wikinotes-linkify.html">the Linkify! article</a>, we talked about
8using Linkify to turn wiki words (those that match a regular expression that we
9have defined) into a <code>content:</code> URI and defining a path to data that
10matched a note belonging to that wiki word. As an example, a matching word like
11<code>ToDoList</code> would be turned into a URI such as
12<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList
13</code> and then acted upon using the VIEW action from the Linkify class.</p>
14
15<p>This article examines how the Android system takes this combination of
16<code>VIEW</code> action and <code>content:</code> URI and finds the correct
17activity to fire in order to do something with the data. It will also explain
18how the other default links created by Linkify, such as web URLs and telephone
19numbers, also result in the correct activity to handle that data type being
20fired. Finally, this article will start to examine the custom
21<code>ContentProvider</code> that has been created to handle WikiNotes data. The
22full description of the ContentProvider and what it does will span a couple more
23articles as well, because there is a lot to cover.</p>
24
25<h3>The Linkify-calls-intent Workflow</h3>
26
27<p>At a high level, the steps for Linkify to invoke an intent, and for the
28resulting activity (if any) to handle it, look like this:</p>
29
30<ol>
31 <li>Linkify is invoked on a TextView to turn matching text patterns into Intent links.</li>
32 <li>Linkify takes over monitoring for those Intent links being selected by the user.</li>
33 <li>When the user selects a link, Linkify calls the VIEW action using the content: URI associated with the link.</li>
34 <li>Android takes the content: URI that represents the data, and looks for a
35ContentProvider registered in the system that matches the URI.</li>
36 <li>If a match is found, Android queries the ContentProvider using the URI,
37and asks what MIME type the data that will be returned from the URI is.</li>
38 <li>Android then looks for an activity registered in the system with an
39intent-filter that matches both the VIEW action, and the MIME type for
40the data represented by the content: URI.</li>
41 <li>Assuming a match is found, Linkify then invokes the intent for
42the URI, at which point the activity takes over, and is handed
43the content: URI.</li>
44 <li>The activity can then use the URI to retrieve the data and act on
45it.</li>
46</ol>
47
48<p>This is actually a simpler process than it
49sounds, and it is quite lightweight as well. Perhaps a more
50understandable statement about how it works might be:</p>
51
52<p>Linkify is used to turn matching text into hot-links. When the user
53selects a hot-link, Android takes the data locator represented by the
54hot-link and looks for a data handler for that data locator. If it
55finds one, it asks for what type of data is returned for that locator.
56It then looks for something registered with the system that handles
57that type of data for the VIEW action, and starts it, including the
58data locator in the request.</p>
59
60<p>The real key here is the MIME type. MIME stands for <a
61href="http://en.wikipedia.org/wiki/MIME">Multipurpose Internet Mail
62Extensions</a> &mdash; a standard for sending attachments over email. The MIME
63type (which is the part Android uses) is a way of describing certain kinds of
64data. That type is then used to look for an Activity that can do something with
65that data type. In this way, ContentProviders and Activities (or other
66IntentReceivers) are decoupled, meaning that a given Content URI might have a
67different ContentProvider to handle it, but could still use the same MIME type
68meaning that the same activity could be called upon to handle the resulting
69data.</p>
70
71<h3>Linkify on a wiki word</h3>
72
73<p>Using the above workflow, let's take a look at exactly how the process
74works in WikiNotes for Android:</p>
75
76<p>First, Linkify is used to turn text matching the wiki word regular expression
77into a link that provides a Content URI for that wiki word, for example
78<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList</code>.</p>
79
80<p>When the user clicks on the wiki word link, Linkify invokes the VIEW
81action on the Content URI. At this point, the Android system takes over
82getting the Intent request to the correct activity.</p>
83
84<p>Next, Android looks for a ContentProvider that has been registered
85with the system to handle URIs matching our Content URI format.</p>
86
87<p>In our case, we have a definition inside
88<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">our application's AndroidManifest.xml</a>
89file that reads:</p>
90
91<pre>&lt;provider name="com.google.android.wikinotes.db.WikiNotesProvider"
92 android:authorities="com.google.android.wikinotes.db.wikinotes" /&gt;</pre>
93
94<p>This establishes that we have a ContentProvider defined in our application
95that provides the "root authority":
96<code>com.google.android.wikinotes.db.wikinotes</code>. This is the first part
97of the Content URI that we create for a wiki word link. Root Authority is just
98another way of thinking about a descriptor that is registered with Android to
99allow requests for certain URLs to be routed to the correct class.</p>
100
101<p>So, the whole definition is that a class called
102<code>com.google.android.wikinotes.db.WikiNotesProvider</code> is registered
103with the system as able to handle the
104<code>com.google.android.wikinotes.db.wikinotes</code> root authority (i.e. URIs
105starting with that identifier).</p>
106
107<p>From here, Android takes the rest of the URI and presents it to that
108ContentProvider. If you look at the
109<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider
110class</a> and scroll to the very bottom, in the static block there, you can see
111the pattern definitions to match the rest of the URL.</p>
112
113<p>In particular, take a look at the two lines:</p>
114
115<pre>URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes", NOTES);
116URI_MATCHER.addURI(WikiNote.WIKINOTES_AUTHORITY, "wikinotes/*", NOTE_NAME);</pre>
117
118<p>These are the definitions of URIs that our ContentProvider recognizes and can
119handle. The first recognizes a full URI of
120<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes</code> and
121associates that with a constant called NOTES. This is used elsewhere in the
122ContentProvider to provide a list of all of the wiki notes in the database when
123the URI is requested.</p>
124
125<p>The second line uses a wildcard &mdash; '*' &mdash; to match a request of the
126form that Linkify will create, e.g.
127<code>content://com.google.android.wikinotes.db.wikinotes/wikinotes/ToDoList
128</code>. In this example, the * matches the ToDoList part of the URI and is
129available to the handler of the request, so that it can fish out the matching
130note for ToDoList and return it as the data. This also associates that match
131with a constant called NOTE_NAME, which again is used as an identifier elsewhere
132in the ContentProvider.</p>
133
134<p>The other matches in this static block are related to forms of
135searching that have been implemented in the WikiNotes for Android
136application, and will be covered in later articles. Likewise, how the
137data is obtained from this matching pattern will be the subject of the
138next article.</p>
139
140<p>For right now we are concerned with the MIME type for the URI. This is
141defined in the <code>getType()</code> method also in the
142<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/src/com/google/android/wikinotes/db/WikiNotesProvider.java">WikiNotesProvider
143class</a> (about halfway through the file). Take a quick look at this. The key
144parts for now are:</p>
145
146<pre>case NOTES:
147 return "vnd.android.cursor.<b>dir</b>/vnd.google.wikinote";</pre>
148
149<p>and</p>
150
151<pre>case NOTE_NAME:
152 return "vnd.android.cursor.<b>item</b>/vnd.google.wikinote";</pre>
153
154<p>These are the same constant names we defined in our pattern
155matchers. In the first case, that of the all notes URI, the MIME type
156returned is <code>vnd.android.cursor.dir/vnd.google.wikinote</code>
157which is like saying an Android list (dir) of Google wiki notes (the
158vnd bit is MIME-speak for "vendor specific definition"). Likewise, in
159the case of a NOTE_NAME match, the MIME type returned is
160<code>vnd.android.cursor.item/vnd.google.wikinote</code> which is
161like saying an Android item of Google wiki notes.</p>
162
163<p>Note that if you define your own MIME data types like this, the
164<code>vnd.android.cursor.dir</code> and <code>vnd.android.cursor.item</code>
165categories should be retained, since they have meaning to the Android
166system, but the actual item types should be changed to reflect your
167particular data type.</p>
168
169<p>So far Android has been able to find a ContentProvider that handles
170the Content URI supplied by the Linkify Intent call, and has queried
171the ContentProvider to find out the MIME types for that URI. The final
172step is to find an activity that can handle the VIEW action for that
173MIME type. Take a look in the the
174<a href="http://code.google.com/p/apps-for-android/source/browse/trunk/WikiNotes/AndroidManifest.xml">AndroidManifest.xml file</a>
175again. Inside the WikiNotes activity definition, you will see:</p>
176
177<pre>&lt;intent-filter&gt;
178 &lt;action name="android.intent.action.VIEW"/&gt;
179 &lt;category name="android.intent.category.DEFAULT"/&gt;
180 &lt;category name="android.intent.category.BROWSABLE"/&gt;
181 &lt;data mimetype="vnd.android.cursor.item/vnd.google.wikinote"/&gt;
182&lt;/intent-filter&gt;</pre>
183
184<p>This is the correct combination of matches for the VIEW action on a
185WikiNote type that is requested from the LINKIFY class. The DEFAULT
186category indicates that the WikiNotes activity should be treated as a
187default handler (a primary choice) for this kind of data, and the
188BROWSABLE category means it can be invoked from a "browser", in this
189case the marked-up Linkified text.</p>
190
191<p>Using this information, Android can match up the VIEW action request
192for the WikiNotes data type with the WikiNotes activity, and can then
193use the WikiNotes activity to handle the request.</p>
194
195<h3>Why do it like this?</h3>
196
197<p>It's quite a trip through the system, and there is a lot to absorb
198here, but this is one of the main reasons I wanted to write WikiNotes
199in the first place. If you follow and understand the steps here, you'll
200have a good grasp of the whole Intents mechanism in Android, and how it
201helps loosely coupled activities cooperate to get things done.</p>
202
203<p>In this case, we could have found another way to detect wiki words
204based on a regular expression, and maybe written our own handler to
205intercept clicks within the TextView and dig out the right data and
206display it. This would seem to accomplish the same functionality just
207as easily as using intents, so what is the advantage to using the full
208Intents mechanism?</p>
209
210<p>In fact there are several advantages:</p>
211
212<p>The most obvious is that because we are using the standard Intent
213based approach, we are not limited to just linking and navigating to
214other wiki notes. We get similar behavior to a number of other data
215types as well. For example, a telephone number or web URL in a wiki
216note will be marked up by Linkify, and using this same mechanism (VIEW
217action on the linked data type) the browser or dialer activities will
218be automatically fired.</p>
219
220<p>It also means that each operation on a wiki note can be treated as a
221separate life cycle by our activity. We are not dealing with swapping
222data in and out of an existing activity - each activity works on a
223particular wiki note and that's all you have to worry about.</p>
224
225<p>Another advantage is that we now have a public activity to handle
226VIEW actions in WikiNotes no matter where the request comes from.
227Another application could request to view a wiki note (perhaps without
228even knowing what kind of data it is) and our activity could start up
229and handle it.</p>
230
231<p>The backstack is automatically maintained for you too. As you
232forward navigate through WikiNotes, Android maintains the history of
233notes visited, and so when you hit the back button you go back to the
234last note you were on. All this is free because we rely on the Android
235intents mechanism.</p>
236
237<p>Finally, if you run WikiNotes for Android and then start DDMS to
238take a look at the Activity threads in the WikiNotes application while
239it is running, you can see that despite what you might think, letting
240Android manage the navigation is very efficient. Create a few linked
241notes, as many links deep as you like, and then follow them. If you
242follow links hundreds of notes deep, you will still only see a handful
243of WikiNotes activities. Android is managing the activities, closing
244the older ones as necessary and using the life cycle to swap data in
245and out.</p>
246
247<h3>Next Time</h3>
248
249<p>This was a long article, but necessarily so. It demonstrates the
250importance of the Intents mechanism and to reinforce the notion that it
251should be used whenever possible for forward navigation, even within a
252single application. Illustrating this is one of the primary reasons I
253wrote WikiNotes for Android in the first place.</p>
254
255<p>In the next article we will look deeper into the ContentProvider and
256examine how it turns a Content URI into a row (or several rows) of data
257that can be used by an activity.</p>