Androidでの効果的なクライアントサーバーインタラクション

StrictModeとANRとの戦い



Gingerbreadのバージョンから、GoogleはAndroidにUIスレッドで実行された長期的な操作を追跡できるメカニズムを追加しました。 このメカニズムの名前はStrictModeです。 なぜこれが行われたのですか?



おそらく、各自がアプリケーションの「アプリケーションが応答していません」ダイアログボックスまたはANRに遭遇した可能性があります。 これは、アプリケーションが入力イベント(キーストローク、画面上のタッチ)に5秒間応答しない場合に発生します。 UIスレッドをブロックするプロセスを監視するために、StrictModeメカニズムが導入されました。 また、Android 3.0以降、StrictModeはUIスレッドでネットワークリクエストを実行しようとする試みを抑制します。



では、AndroidはUIスレッドの外部のネットワークにアクセスするためにどのようなメカニズムを提供していますか?



AsyncTask



おそらく、ネットワーク操作の実行に役立つ最も一般的なメカニズムの1つはAsyncTaskです。 例を考えてみましょう:

画像

一見、すべては問題ありませんが、いくつかの欠点があります。



3.0から、上記の問題のほとんどを解決できるレスキューメカニズムが登場しました。 しかし、プラットフォーム要件が3.0より低い場合はどうでしょうか? このメカニズムがバックポートされたサポートライブラリを絶望して使用しないでください。



ローダー



このメカニズムが優れている理由:



ローダーを使用してネットワーク経由でデータを要求する方法を見てみましょう。

画像

どのように機能しますか? ローダー内で、ネットワークストリームが起動され、結果が解析されてContentProviderに送信され、DataStorage(メモリ、ファイルシステム、sqliteデータベースなど)に保存され、データが変更されたことがローダーに通知されます。 ローダーは、新しいデータのContentProviderをポーリングし、アクティビティ(フラグメント)に返します。 ネットワークストリームをサービスに置き換えると、アプリケーションが最小化されていてもユーザーがデータを受信することを保証できます(サービスはバックグラウンドプロセスよりも優先度が高いため)。



このアプローチの利点は何ですか:



実装例
public abstract class AbstractRestLoader extends Loader<Cursor> { private static final int CORE_POOL_SIZE = Android.getCpuNumCores() * 4; private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 4; private static final int POOL_KEEP_ALIVE = 1; private static final BlockingQueue<Runnable> sPoolWorkQueue; private static final ThreadFactory sThreadFactory; private static final ExecutorService sThreadPoolExecutor; private static final AsyncHttpClient sDefaultHttpClient; private static final Handler sUiHandler; static { sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(CORE_POOL_SIZE * 2); sThreadFactory = new LoaderThreadFactory(); sThreadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAX_POOL_SIZE, POOL_KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory ); sDefaultHttpClient = new AsyncHttpClient(); sUiHandler = new Handler(Looper.getMainLooper()); } private final AsyncHttpClient mHttpClient; private final HttpMethod mRestMethod; private final Uri mContentUri; private final ContentObserver mObserver; private String[] mProjection; private String mWhere; private String[] mWhereArgs; private String mSortOrder; private boolean mLoadBeforeRequest; private FutureTask<?> mLoaderTask; private AsyncHttpRequest mRequest; private Cursor mCursor; private boolean mContentChanged; public AbstractRestLoader(Context context, HttpMethod request, Uri contentUri) { super(context); mHttpClient = onInitHttpClient(); mRestMethod = request; mContentUri = contentUri; mObserver = new CursorObserver(sUiHandler); } public Uri getContentUri() { return mContentUri; } public AbstractRestLoader setProjection(String[] projection) { mProjection = projection; return this; } public AbstractRestLoader setWhere(String where, String[] whereArgs) { mWhere = where; mWhereArgs = whereArgs; return this; } public AbstractRestLoader setSortOrder(String sortOrder) { mSortOrder = sortOrder; return this; } public AbstractRestLoader setLoadBeforeRequest(boolean load) { mLoadBeforeRequest = load; return this; } @Override public void deliverResult(Cursor cursor) { final Cursor oldCursor = mCursor; mCursor = cursor; if (mCursor != null) { mCursor.registerContentObserver(mObserver); } if (isStarted()) { super.deliverResult(cursor); mContentChanged = false; } if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) { oldCursor.unregisterContentObserver(mObserver); oldCursor.close(); } } @Override protected void onStartLoading() { if (mCursor == null || mContentChanged) { forceLoad(); } else { deliverResult(mCursor); } } @Override protected void onForceLoad() { cancelLoadInternal(); if (mLoadBeforeRequest) { reloadCursorInternal(); } restartRequestInternal(); } @Override protected void onReset() { cancelLoadInternal(); if (mCursor != null && !mCursor.isClosed()) { mCursor.close(); } mCursor = null; } protected AsyncHttpClient onInitHttpClient() { return sDefaultHttpClient; } protected void onCancelLoad() { } protected void onException(Exception e) { } protected void deliverResultBackground(final Cursor cursor) { sUiHandler.post(new Runnable() { @Override public void run() { deliverResult(cursor); } }); } protected void deliverExceptionBackground(final Exception e) { sUiHandler.post(new Runnable() { @Override public void run() { onException(e); } }); } protected abstract void onParseInBackground(HttpHead head, InputStream is); protected Cursor onLoadInBackground(Uri contentUri, String[] projection, String where, String[] whereArgs, String sortOrder) { return getContext().getContentResolver().query(contentUri, projection, where, whereArgs, sortOrder); } private void reloadCursorInternal() { if (mLoaderTask != null) { mLoaderTask.cancel(true); } mLoaderTask = new FutureTask<Void>(new Callable<Void>() { @Override public Void call() throws Exception { deliverResultBackground(onLoadInBackground(mContentUri, mProjection, mWhere, mWhereArgs, mSortOrder)); return null; } }); sThreadPoolExecutor.execute(mLoaderTask); } private void restartRequestInternal() { if (mRequest != null) { mRequest.cancel(); } mRequest = mHttpClient.execute(mRestMethod, new AsyncHttpCallback() { @Override public void onSuccess(HttpHead head, InputStream is) { onParseInBackground(head, is); } @Override public void onException(URI uri, Exception e) { deliverExceptionBackground(e); } }); } private void cancelLoadInternal() { onCancelLoad(); if (mLoaderTask != null) { mLoaderTask.cancel(true); mLoaderTask = null; } if (mRequest != null) { mRequest.cancel(); mRequest = null; } } private static final class LoaderThreadFactory implements ThreadFactory { private final AtomicLong mId = new AtomicLong(1); @Override public Thread newThread(Runnable r) { final Thread thread = new Thread(r); thread.setName("LoaderThread #" + mId.getAndIncrement()); return thread; } } private final class CursorObserver extends ContentObserver { public CursorObserver(Handler handler) { super(handler); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { onChange(selfChange, null); } @Override public void onChange(boolean selfChange, Uri uri) { if (isStarted()) { reloadCursorInternal(); } else { mContentChanged = true; } } } }
      
      







使用例
 public class MessageActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { private ListView mListView; private CursorAdapter mListAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ac_message_list); mListView = (ListView) findViewById(android.R.id.list); mListAdapter = new CursorAdapterImpl(getApplicationContext()); getSupportLoaderManager().initLoader(R.id.message_loader, null, this); } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new AbstractRestLoader(getApplicationContext(), new HttpGet("API URL"), null) { @Override protected void onParseInBackground(HttpHead head, InputStream is) { try { getContext().getContentResolver().insert( Messages.BASE_URI, new MessageParser().parse(IOUtils.toString(is)) ); } catch (IOException e) { deliverExceptionBackground(e); } } @Override protected void onException(Exception e) { Logger.error(e); } }.setLoadBeforeRequest(true); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { mListAdapter.swapCursor(data); } @Override public void onLoaderReset(Loader<Cursor> loader) { mListAdapter.swapCursor(null); } }
      
      









PS

Android RESTクライアントアプリケーションの開発

Android開発者-ローダー

Android開発者-プロセスとスレッド



All Articles