AsyncQueryHandler.java revision 3b7c3c6b551b95c58849e40b53b01d38714bb782
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.content; 18 19import android.database.Cursor; 20import android.net.Uri; 21import android.os.Handler; 22import android.os.HandlerThread; 23import android.os.Looper; 24import android.os.Message; 25import android.util.Log; 26 27import java.lang.ref.WeakReference; 28 29/** 30 * A helper class to help make handling asynchronous {@link ContentResolver} 31 * queries easier. 32 */ 33public abstract class AsyncQueryHandler extends Handler { 34 private static final String TAG = "AsyncQuery"; 35 private static final boolean localLOGV = false; 36 37 private static final int EVENT_ARG_QUERY = 1; 38 private static final int EVENT_ARG_INSERT = 2; 39 private static final int EVENT_ARG_UPDATE = 3; 40 private static final int EVENT_ARG_DELETE = 4; 41 42 /* package */ final WeakReference<ContentResolver> mResolver; 43 44 private static Looper sLooper = null; 45 46 private Handler mWorkerThreadHandler; 47 48 protected static final class WorkerArgs { 49 public Uri uri; 50 public Handler handler; 51 public String[] projection; 52 public String selection; 53 public String[] selectionArgs; 54 public String orderBy; 55 public Object result; 56 public Object cookie; 57 public ContentValues values; 58 } 59 60 protected class WorkerHandler extends Handler { 61 public WorkerHandler(Looper looper) { 62 super(looper); 63 } 64 65 @Override 66 public void handleMessage(Message msg) { 67 final ContentResolver resolver = mResolver.get(); 68 if (resolver == null) return; 69 70 WorkerArgs args = (WorkerArgs) msg.obj; 71 72 int token = msg.what; 73 int event = msg.arg1; 74 75 switch (event) { 76 case EVENT_ARG_QUERY: 77 Cursor cursor; 78 try { 79 cursor = resolver.query(args.uri, args.projection, 80 args.selection, args.selectionArgs, 81 args.orderBy); 82 // Calling getCount() causes the cursor window to be filled, 83 // which will make the first access on the main thread a lot faster. 84 if (cursor != null) { 85 cursor.getCount(); 86 } 87 } catch (Exception e) { 88 Log.d(TAG, e.toString()); 89 cursor = null; 90 } 91 92 args.result = cursor; 93 break; 94 95 case EVENT_ARG_INSERT: 96 args.result = resolver.insert(args.uri, args.values); 97 break; 98 99 case EVENT_ARG_UPDATE: 100 args.result = resolver.update(args.uri, args.values, args.selection, 101 args.selectionArgs); 102 break; 103 104 case EVENT_ARG_DELETE: 105 args.result = resolver.delete(args.uri, args.selection, args.selectionArgs); 106 break; 107 108 } 109 110 // passing the original token value back to the caller 111 // on top of the event values in arg1. 112 Message reply = args.handler.obtainMessage(token); 113 reply.obj = args; 114 reply.arg1 = msg.arg1; 115 116 if (localLOGV) { 117 Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1 118 + ", reply.what=" + reply.what); 119 } 120 121 reply.sendToTarget(); 122 } 123 } 124 125 public AsyncQueryHandler(ContentResolver cr) { 126 super(); 127 mResolver = new WeakReference<ContentResolver>(cr); 128 synchronized (AsyncQueryHandler.class) { 129 if (sLooper == null) { 130 HandlerThread thread = new HandlerThread("AsyncQueryWorker"); 131 thread.start(); 132 133 sLooper = thread.getLooper(); 134 } 135 } 136 mWorkerThreadHandler = createHandler(sLooper); 137 } 138 139 protected Handler createHandler(Looper looper) { 140 return new WorkerHandler(looper); 141 } 142 143 /** 144 * This method begins an asynchronous query. When the query is done 145 * {@link #onQueryComplete} is called. 146 * 147 * @param token A token passed into {@link #onQueryComplete} to identify 148 * the query. 149 * @param cookie An object that gets passed into {@link #onQueryComplete} 150 * @param uri The URI, using the content:// scheme, for the content to 151 * retrieve. 152 * @param projection A list of which columns to return. Passing null will 153 * return all columns, which is discouraged to prevent reading data 154 * from storage that isn't going to be used. 155 * @param selection A filter declaring which rows to return, formatted as an 156 * SQL WHERE clause (excluding the WHERE itself). Passing null will 157 * return all rows for the given URI. 158 * @param selectionArgs You may include ?s in selection, which will be 159 * replaced by the values from selectionArgs, in the order that they 160 * appear in the selection. The values will be bound as Strings. 161 * @param orderBy How to order the rows, formatted as an SQL ORDER BY 162 * clause (excluding the ORDER BY itself). Passing null will use the 163 * default sort order, which may be unordered. 164 */ 165 public void startQuery(int token, Object cookie, Uri uri, 166 String[] projection, String selection, String[] selectionArgs, 167 String orderBy) { 168 // Use the token as what so cancelOperations works properly 169 Message msg = mWorkerThreadHandler.obtainMessage(token); 170 msg.arg1 = EVENT_ARG_QUERY; 171 172 WorkerArgs args = new WorkerArgs(); 173 args.handler = this; 174 args.uri = uri; 175 args.projection = projection; 176 args.selection = selection; 177 args.selectionArgs = selectionArgs; 178 args.orderBy = orderBy; 179 args.cookie = cookie; 180 msg.obj = args; 181 182 mWorkerThreadHandler.sendMessage(msg); 183 } 184 185 /** 186 * Attempts to cancel operation that has not already started. Note that 187 * there is no guarantee that the operation will be canceled. They still may 188 * result in a call to on[Query/Insert/Update/Delete]Complete after this 189 * call has completed. 190 * 191 * @param token The token representing the operation to be canceled. 192 * If multiple operations have the same token they will all be canceled. 193 */ 194 public final void cancelOperation(int token) { 195 mWorkerThreadHandler.removeMessages(token); 196 } 197 198 /** 199 * This method begins an asynchronous insert. When the insert operation is 200 * done {@link #onInsertComplete} is called. 201 * 202 * @param token A token passed into {@link #onInsertComplete} to identify 203 * the insert operation. 204 * @param cookie An object that gets passed into {@link #onInsertComplete} 205 * @param uri the Uri passed to the insert operation. 206 * @param initialValues the ContentValues parameter passed to the insert operation. 207 */ 208 public final void startInsert(int token, Object cookie, Uri uri, 209 ContentValues initialValues) { 210 // Use the token as what so cancelOperations works properly 211 Message msg = mWorkerThreadHandler.obtainMessage(token); 212 msg.arg1 = EVENT_ARG_INSERT; 213 214 WorkerArgs args = new WorkerArgs(); 215 args.handler = this; 216 args.uri = uri; 217 args.cookie = cookie; 218 args.values = initialValues; 219 msg.obj = args; 220 221 mWorkerThreadHandler.sendMessage(msg); 222 } 223 224 /** 225 * This method begins an asynchronous update. When the update operation is 226 * done {@link #onUpdateComplete} is called. 227 * 228 * @param token A token passed into {@link #onUpdateComplete} to identify 229 * the update operation. 230 * @param cookie An object that gets passed into {@link #onUpdateComplete} 231 * @param uri the Uri passed to the update operation. 232 * @param values the ContentValues parameter passed to the update operation. 233 */ 234 public final void startUpdate(int token, Object cookie, Uri uri, 235 ContentValues values, String selection, String[] selectionArgs) { 236 // Use the token as what so cancelOperations works properly 237 Message msg = mWorkerThreadHandler.obtainMessage(token); 238 msg.arg1 = EVENT_ARG_UPDATE; 239 240 WorkerArgs args = new WorkerArgs(); 241 args.handler = this; 242 args.uri = uri; 243 args.cookie = cookie; 244 args.values = values; 245 args.selection = selection; 246 args.selectionArgs = selectionArgs; 247 msg.obj = args; 248 249 mWorkerThreadHandler.sendMessage(msg); 250 } 251 252 /** 253 * This method begins an asynchronous delete. When the delete operation is 254 * done {@link #onDeleteComplete} is called. 255 * 256 * @param token A token passed into {@link #onDeleteComplete} to identify 257 * the delete operation. 258 * @param cookie An object that gets passed into {@link #onDeleteComplete} 259 * @param uri the Uri passed to the delete operation. 260 * @param selection the where clause. 261 */ 262 public final void startDelete(int token, Object cookie, Uri uri, 263 String selection, String[] selectionArgs) { 264 // Use the token as what so cancelOperations works properly 265 Message msg = mWorkerThreadHandler.obtainMessage(token); 266 msg.arg1 = EVENT_ARG_DELETE; 267 268 WorkerArgs args = new WorkerArgs(); 269 args.handler = this; 270 args.uri = uri; 271 args.cookie = cookie; 272 args.selection = selection; 273 args.selectionArgs = selectionArgs; 274 msg.obj = args; 275 276 mWorkerThreadHandler.sendMessage(msg); 277 } 278 279 /** 280 * Called when an asynchronous query is completed. 281 * 282 * @param token the token to identify the query, passed in from 283 * {@link #startQuery}. 284 * @param cookie the cookie object that's passed in from {@link #startQuery}. 285 * @param cursor The cursor holding the results from the query. 286 */ 287 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 288 // Empty 289 } 290 291 /** 292 * Called when an asynchronous insert is completed. 293 * 294 * @param token the token to identify the query, passed in from 295 * {@link #startInsert}. 296 * @param cookie the cookie object that's passed in from 297 * {@link #startInsert}. 298 * @param uri the uri returned from the insert operation. 299 */ 300 protected void onInsertComplete(int token, Object cookie, Uri uri) { 301 // Empty 302 } 303 304 /** 305 * Called when an asynchronous update is completed. 306 * 307 * @param token the token to identify the query, passed in from 308 * {@link #startUpdate}. 309 * @param cookie the cookie object that's passed in from 310 * {@link #startUpdate}. 311 * @param result the result returned from the update operation 312 */ 313 protected void onUpdateComplete(int token, Object cookie, int result) { 314 // Empty 315 } 316 317 /** 318 * Called when an asynchronous delete is completed. 319 * 320 * @param token the token to identify the query, passed in from 321 * {@link #startDelete}. 322 * @param cookie the cookie object that's passed in from 323 * {@link #startDelete}. 324 * @param result the result returned from the delete operation 325 */ 326 protected void onDeleteComplete(int token, Object cookie, int result) { 327 // Empty 328 } 329 330 @Override 331 public void handleMessage(Message msg) { 332 WorkerArgs args = (WorkerArgs) msg.obj; 333 334 if (localLOGV) { 335 Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what 336 + ", msg.arg1=" + msg.arg1); 337 } 338 339 int token = msg.what; 340 int event = msg.arg1; 341 342 // pass token back to caller on each callback. 343 switch (event) { 344 case EVENT_ARG_QUERY: 345 onQueryComplete(token, args.cookie, (Cursor) args.result); 346 break; 347 348 case EVENT_ARG_INSERT: 349 onInsertComplete(token, args.cookie, (Uri) args.result); 350 break; 351 352 case EVENT_ARG_UPDATE: 353 onUpdateComplete(token, args.cookie, (Integer) args.result); 354 break; 355 356 case EVENT_ARG_DELETE: 357 onDeleteComplete(token, args.cookie, (Integer) args.result); 358 break; 359 } 360 } 361} 362