📜 ⬆️ ⬇️

Efficient client-server interaction in Android

StrictMode and the fight against ANR


Starting with the Gingerbread version, Google has added a mechanism to Android that allows you to track long-term operations performed in the UI stream. The name for this mechanism is StrictMode. What was it done for?

Each of you may have come across an Application Not Responding dialog box (the application is not responding) or ANR in applications. This happens when the application does not respond to input events (keystrokes, touch on the screen) for 5 seconds. To track the processes blocking the UI stream, the StrictMode mechanism was introduced. And, starting with Android 3.0, StrictMode stops attempts to perform a network request in the UI stream.

So, what mechanisms does Android provide for accessing the network outside the UI stream?

AsyncTask


Probably one of the most common mechanisms to help perform network operations is AsyncTask. Consider an example:
image
At first glance, everything is fine, but there are several drawbacks:

Starting from 3.0, a mechanism comes to our rescue that allows us to solve most of the problems described above. But what if the platform requirements are below 3.0? Do not despair and use the support-library in which this mechanism was backported.
')

Loaders


What is so good about this mechanism:

Let's look at how you can use the Loader to request data over the network:
image
How it works? Inside the Loader, the network stream is started, the result is parsed and sent to the ContentProvider, which saves them to the DataStorage (it can be memory, file system, sqlite database) and notifies the Loader that the data has been changed. The loader, in turn, polls the ContentProvider for new data and returns it to the Activity (Fragment). If we replace the network flow with a service, we can guarantee that the user will receive the data even if the application has been minimized (as the Service has a higher priority than the background process).

What is the advantage of this approach:

Implementation example
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; } } } } 


Usage example
 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
Developing Android REST client applications
Android Developers - Loaders
Android Developers - Processes and Threads

Source: https://habr.com/ru/post/175455/


All Articles