レスポンシブAndroidアプリ、または写真をアップロードする1001の方法

Androidアプリケーションの開発におけるマルチスレッドの実装については、すでに多くのことが書かれています。 同じ記事で、ユーザーに迷惑をかける理由を与えずに、今日のダウンロード/読み取り/保存/カウントの一般的な方法を比較したいと思います。 特定の決定がいつ適切になるのか、何もしないほうがよいのかを理解するようにしてください。 Androidアプリケーションに関しては、Threadクラスやjava.util.concurrentパッケージなどの通常のものでは不十分な理由を示すようにします。



この記事には、各アプローチの実装のすべての詳細を強調するタスクはありませんが、基本を告げずにそれらを比較することは不可能です。 したがって...



スレッド


JavaからAndroid APIに移行したThreadクラスは、おそらく新しいスレッドを開始する最も簡単な方法です。 これを行う方法の例をいくつか示します。Threadから継承者を作成するか、Runnableインターフェイスを実装するオブジェクトをThreadクラスのインスタンスに渡すことができます。



例1.拡張スレッド。
class WorkingThread extends Thread{ @Override public void run() { //  } }
      
      













例2.実行可能。
 class WorkingClass implements Runnable{ @Override public void run() { //  } } WorkingClass workingClass = new WorkingClass(); Thread thread = new Thread(workingClass); thread.start();
      
      







原則として、必要な操作を実行した後、結果をユーザーに提供する必要があります。 ただし、別のスレッドからUI要素を取得してアクセスすることはできません。 Androidマルチスレッドモデルのため、インターフェイス要素の状態の変更は、これらの要素が作成されたスレッドからのみ許可されます。そうでない場合、CalledFromWrongThreadExceptionがスローされます。 この場合、Android APIは複数のソリューションを一度に提供します。



例1.#postを表示(実行可能なアクション)。
 public class MainActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); WorkingClass workingClass = new WorkingClass(); Thread thread = new Thread(workingClass); thread.start(); } class WorkingClass implements Runnable{ @Override public void run() { //  //  UI   Runnable textView.post(new Runnable() { @Override public void run() { textView.setText("The job is done!"); } }); } } }
      
      









例2.アクティビティ#runOnUiThread(実行可能なアクション)。
 public class MainActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); WorkingClass workingClass = new WorkingClass(); Thread thread = new Thread(workingClass); thread.start(); } class WorkingClass implements Runnable{ @Override public void run() { //  //  UI   Runnable MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { textView.setText("The job is done!"); } }); } } }
      
      









例3.ハンドラー。
 public class MainActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); WorkingClass workingClass = new WorkingClass(true); Thread thread = new Thread(workingClass); thread.start(); } class WorkingClass implements Runnable{ public static final int SUCCESS = 1; public static final int FAIL = 2; private boolean dummyResult; public WorkingClass(boolean dummyResult){ this.dummyResult = dummyResult; } @Override public void run() { //  //    if (dummyResult){ //      uiHandler.sendEmptyMessage(SUCCESS); } else { //       Message msg = Message.obtain(); msg.what = FAIL; msg.obj = "An error occurred"; uiHandler.sendMessage(msg); } } } Handler uiHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case WorkingClass.SUCCESS: textView.setText("Success"); return true; case WorkingClass.FAIL: textView.setText((String)msg.obj); return true; } return false; } }); }
      
      







一般的には簡単ですが、インターフェイス要素と積極的にやり取りすることになると、コードはRunnableインターフェイスのヒープまたはかなり大きなHandlerクラスになります。 メインストリームとバックグラウンドストリームの同期作業を簡素化するために、AsyncTaskクラスはAndroid 1.5で既に提案されています



AsyncTask


AsyncTaskを使用するには、パラメーター化された型でその下位クラスを作成し、必要なメソッドをオーバーライドする必要があります。 開始後、AsyncTaskは次の順序でメソッドを呼び出します:onPreExecute()、doInBackground(Params ...)、onPostExecute(Result)。最初と最後はUIスレッドで呼び出され、2番目は別のスレッドで推測できます。 さらに、AsyncTaskクラスにより、UIスレッドはpublishProgress(Progress ...)メソッドを使用して実行の進行状況をUIスレッドに通知できます。このメソッドは、UIスレッドでonProgressUpdate(Progress ...)を呼び出します。



AsyncTaskの例
 public class MainActivity extends Activity { private static final String IMAGE_URL = "http://eastbancgroup.com/images/ebtLogo.gif"; TextView textView; ImageView imageView; ProgressDialog progressDialog; DownloadTask downloadTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); imageView = (ImageView)findViewById(R.id.imageView); downloadTask = new DownloadTask(); // ,      downloadTask.execute(IMAGE_URL); } @Override protected void onStop() { //   , //  Activity downloadTask.cancel(true); super.onStop(); } /* *    AsyncTask<Params, Progress, Result> *  ,     generic-. * Params -   .     String, .. *   url  * Progress -  ,      . *    Integer. * Result -  .    Drawable. */ class DownloadTask extends AsyncTask<String, Integer, Drawable>{ @Override protected void onPreExecute() { //    progressDialog = new ProgressDialog(MainActivity.this); progressDialog.setIndeterminate(false); progressDialog.setMax(100); progressDialog.setProgress(0); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Downloading Image"); progressDialog.show(); } @Override protected Drawable doInBackground(String... params) { //       //  URLConnection int count; try { URL url = new URL(params[0]); URLConnection conection = url.openConnection(); conection.connect(); int lenghtOfFile = conection.getContentLength(); InputStream input = new BufferedInputStream(url.openStream(), 8192); OutputStream output = new FileOutputStream("/sdcard/downloadedfile.jpg"); byte data[] = new byte[256]; long total = 0; while ((count = input.read(data)) != -1) { //,     if (isCancelled()){ return null; } total += count; output.write(data, 0, count); //  . // ,     //       //onProgressUpdate    publishProgress((int)((total*100)/lenghtOfFile)); } output.flush(); output.close(); input.close(); } catch (Exception e) { Log.e("Error: ", e.getMessage()); } String imagePath = Environment.getExternalStorageDirectory().toString() + "/downloadedfile.jpg"; return Drawable.createFromPath(imagePath); } @Override protected void onProgressUpdate(Integer... progress) { progressDialog.setProgress(progress[0]); } //     @Override protected void onPostExecute(Drawable result) { imageView.setImageDrawable(result); progressDialog.dismiss(); } //     onPostExecute, //      //AsyncTask#cancel(boolean mayInterruptIfRunning) @Override protected void onCancelled() { } } }
      
      









この画像読み込みの例は、準備、バックグラウンド操作、更新の進行、最終アクション、作業の停止など、AsyncTaskクラスによって提供されるすべての可能性を示しています。 また、これらの各段階で、開発者はバックグラウンドスレッドとメインスレッドの同期について心配する必要はありません。



多くの場合、AsyncTaskはThreadクラスを使用してスレッドを作成する方が便利ですが、マルチスレッドを実装する最初の方法の方が有利な場合があります。 私たちの意見では、マルチスレッドを実装する方法を選択する際に考慮すべき重要な違いは次のとおりです。



2番目のポイントは特に重要です。 事実、このタスクの作業モデルのために、それらに基づいて長寿命のバックグラウンドプロセス(タイマーなど)を作成することは不可能です。 このようなAsyncTaskのインスタンスはキューを詰まらせ、後続のタスクの実行を妨げます。 しかし、多数のスレッドとハンドラーを使用すると、逆に、間隔を置いてコードを実行するのは非常に簡単です。



タイマーの例。
 public class MainActivity extends Activity { TextView textView; private int counter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); counter = 0; textView = (TextView)findViewById(R.id.hello); //  new Thread(new WorkingClass()).start(); } class WorkingClass implements Runnable{ public static final int RELAUNCH = 1; private static final int DELAY = 1000; @Override public void run() { //  //      1000ms uiHandler.sendEmptyMessageDelayed(RELAUNCH, DELAY); } } Handler uiHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { //  if (msg.what == WorkingClass.RELAUNCH){ textView.setText("Times: "+counter); counter++; new Thread(new WorkingClass()).start(); return true; } return false; } }); }
      
      







注:実際には、AsyncTask#executeOnExecutor(Executor exec、Params ... params)メソッドhttps://developer.android.com/reference/android/os/AsyncTask.htmlを使用して、複数のAsyncTaskを並行して実行できます。 。



前の例では、特定のアクティビティのコンテキストでThreadとAsyncTaskの両方が使用されています。 ほとんどの場合、これは正常ですが、このようなモデルは特定の問題を引き起こす可能性があります。 バックグラウンドではありますが、AsyncTaskまたはThreadを使用すると、不要になったアクティビティのインスタンスをガベージコレクターで削除できないことを理解する必要があります。 また、たとえば、デバイスの画面を回すと、非常に簡単に発生します。 画面の向きが変更されるたびに、新しいアクティビティが作成され、そのたびにAsyncTaskが呼び出されます。 ダウンロードしたイメージのサイズが大きいほど、アプリケーションはOutOfMemoryErrorエラーでより速く閉じます。 この例よりも悪いのは、おそらく、多くの教育記事で示されているように、匿名クラスの使用です。 新しいタスクまたはスレッドへのリンクを保存せずに、同じアクティビティを閉じたときに実行を停止するなど、プロセスの進行を制御する機能を自分で奪います。



合計:


ThreadクラスとAsyncTaskクラスの比較から、いくつかの結論を導き出すことができます。

スレッドの使用が正当化されるタスク:



AsyncTaskの使用が正当化されるタスク:



ThreadおよびAsyncTaskを使用した作業に課せられる主な条件:作業がアクティビティ/フラグメントのコンテキストで開始された場合、アクティビティ/フラグメントが停止した後、できるだけ早く終了する必要があります。



ローダー


データ操作には種類があり、その実行は、アプリケーションのメインスレッドでは許可されますが、インターフェイスの速度を著しく低下させたり、 ANRメッセージを発生させることさえあります 。 このような操作の良い例は、データベース/ファイルからの読み取りです。 最近まで、すでに説明したThreadおよびAsyncTaskを使用してデータベースを操作することは良い習慣でしたが、Loader 3.0やLoaderManagerなどのクラスが追加されました。その目的は、データのActivityまたはFragmentへの非同期読み込みを簡素化することです。 古いプラットフォームの場合、同じクラスがandroidサポートライブラリで利用可能です。



ローダーを使用する原則は次のとおりです。



1. Loaderクラスまたはその標準の子孫を拡張する独自のクラスを作成する必要があります。

2.ジェネリックタイプDデータのロードを実装する

3.アクティビティで、LoaderManagerへのリンクを取得し、ローダーを初期化して、ローダーとLoaderManager.LoaderCallbacksへのコールバックをマネージャーに渡します。



標準のCursorLoaderクラスを使用して、電話の連絡先のリストを表示する方法の例を次に示します。



ローダーの例。
 public class MainActivity extends ListActivity implements LoaderCallbacks<Cursor> { //     static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, }; private static final int LOADER_ID = 1; private SimpleCursorAdapter adapter; TextView textview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //,      textview = (TextView)findViewById(R.id.loading); //  ,     getListView().setVisibility(View.GONE); //  ListView adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(adapter); // Loader' //  id Loader'  callback LoaderManager lm = getLoaderManager(); lm.initLoader(LOADER_ID, null, this); } //    Loader,   //      @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Uri baseUri = Contacts.CONTENT_URI; String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(this, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } //  ,     //  ,      @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { switch (loader.getId()) { case LOADER_ID: adapter.swapCursor(cursor); textview.setVisibility(View.GONE); getListView().setVisibility(View.VISIBLE); break; } } @Override public void onLoaderReset(Loader<Cursor> loader) { adapter.swapCursor(null); } }
      
      







アプリケーションマニフェストで連絡先を読み取るための適切なアクセス許可を指定することを忘れないでください。



合計:


ローダーテンプレートの使用は、表示を担当するアプリケーションのコンポーネント(アクティビティ、フラグメント)と密接に関連しているため、データの読み込み操作の実行時間は、これらのコンポーネントの寿命に匹敵するはずです。



サービスとIntentService


サービスは、Androidアプリケーションのコンポーネントの1つです。 サービス自体は、独立したプロセスまたは独立したスレッドではありません。 ただし、サービスには独自のライフサイクルがあり、サービスで長期的な操作を実行するのに適しています。 サービスのコンテキストで起動された追加のスレッドは、アプリケーションを介したユーザーのナビゲーションを妨げることなく実行できます。 通常、サービスとアプリケーションの他のコンポーネントとの間の通信には、ServiceConnection / IBinderインターフェイスまたはブロードキャストメッセージの2つの方法が使用されます。 最初の方法の本質は、サービスの実行中のインスタンスへのリンクを取得することです。 これは、このメソッドがマルチタスクの問題を何らかの形で解決するということではなく、サービスの管理に適しています。 また、ブロードキャストメッセージを使用した通信はスレッドセーフであるため、この例では考慮されます。



サービス例。
 public class BackgroundService extends Service { public static final String CHANNEL = BackgroundService.class.getSimpleName()+".broadcast"; //     , //     Intent @Override public int onStartCommand(Intent intent, int flags, int startId) { //        sendResult(); return Service.START_NOT_STICKY; } //     , // Broadcast private void sendResult() { Intent intent = new Intent(CHANNEL); sendBroadcast(intent); } @Override public IBinder onBind(Intent intent) { return null; } }
      
      







 public class MainActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); //     registerReceiver(receiver, new IntentFilter(BackgroundService.CHANNEL)); // ,    Intent Intent intent = new Intent(this, BackgroundService.class); startService(intent); } @Override protected void onStop() { //    unregisterReceiver(receiver); super.onStop(); } private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { textView.setText("Message from Service"); } }; }
      
      







サービスとアクティビティは、プロジェクトマニフェストで宣言する必要があることを忘れないでください。



さらに、Android APIはIntentServiceクラスを提供します。これは標準サービスを拡張しますが、別のストリームで転送されたデータの処理を実行します。 新しいリクエストが到着すると、IntentServiceは新しいスレッドを作成し、IntentService#onHandleIntent(Intent intent) https://developer.android.com/reference/android/app/IntentService.html#onHandleIntent (android.content.Intent)を呼び出し、再定義のみ可能です。 新しいリクエストが到着したときに前のリクエストの処理がまだ終了していない場合、リクエストはキューに入れられます。



IntentServiceの例。
 public class DownloadService extends IntentService { public DownloadService() { super("DownloadService"); } public static final String CHANNEL = DownloadService.class.getSimpleName()+".broadcast"; private void sendResult() { Intent intent = new Intent(CHANNEL); sendBroadcast(intent); } @Override public IBinder onBind(Intent intent) { return null; } //        @Override protected void onHandleIntent(Intent intent) { //  //     sendResult(); } }
      
      





 public class MainActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.hello); //     registerReceiver(receiver, new IntentFilter(DownloadService.CHANNEL)); // ,    Intent Intent intent = new Intent(this, DownloadService.class); startService(intent); } @Override protected void onStop() { //    unregisterReceiver(receiver); super.onStop(); } private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { textView.setText("Message from Service"); } }; }
      
      









合計:


通常、サービスのライフサイクルはアクティビティよりも長くなります。 一度開始すると、サービスは動作しなくなるまで生き続け、その後は自動的に停止します。 開発者は基本的に、着信メッセージ(意図)の目的の処理を整理するだけで済みます。比較、キューの作成など、各操作の完了に関するメッセージの送信です。

例からわかるように、ブロードキャストメッセージの送信元は重要ではありません。主なことは、メインストリームで受信されることです。



ダウンロードマネージャー

Android API 9以降、DowloadManagerシステムサービスのおかげで、ネットワーク経由でファイルをダウンロードおよび保存するタスクがさらに簡単になりました。 必要なことは、このUriサービスを転送することだけです。必要に応じて、ダウンロード中およびダウンロード後に通知領域に表示されるテキストを指定し、DownloadManagerが送信できるイベントをサブスクライブします。このサービスは、エラーに応答して、接続の確立を処理し、ダウンロードを再開し、通知バーで通知を作成し、そしてもちろん、バックグラウンドストリーム自体でファイルをダウンロードします。



DownloadManagerの例。


DownloadManagerの例
 public class MainActivity extends Activity { private static final String IMAGE_URL = "http://eastbancgroup.com/images/ebtLogo.gif"; ImageView imageView; DownloadManager downloadManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView)findViewById(R.id.imageView); //   DownloadManager  downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); //   Request request = new Request(Uri.parse(IMAGE_URL)); request.setTitle("Title"); //   request.setDescription("My description"); //   request.setMimeType("application/my-mime"); //mine type   //  ,  , //-      request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); //    downloadManager.enqueue(request); } @Override protected void onResume() { super.onResume(); //     registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED)); }; @Override protected void onPause() { super.onPause(); //    unregisterReceiver(receiver); }; BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //  ,    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)){ long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); Cursor cursor = dm.query(query); if (cursor.moveToFirst()){ int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(columnIndex)) { String uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); imageView.setImageURI(Uri.parse(uriString)); } } //     } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(action)){ DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); //        , //       ,   //  long[] ids = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(ids); Cursor cursor = dm.query(query); int idIndex = cursor.getColumnIndex(DownloadManager.COLUMN_ID); if (cursor.moveToFirst()){ do { //    id   //   long downloadId = cursor.getLong(idIndex); } while (cursor.moveToNext()); } } } }; }
      
      







DownloadManagerには1つの機能があります。 実際、予想に反してファイルのアップロードが成功したという通知をクリックすると、DownloadManager.ACTION_NOTIFICATION_CLICKEDなどのブロードキャストメッセージは送信されません。 しかし、代わりに、サービスはこのクリックを処理できるアクティビティを見つけようとします。 したがって、このイベントに応答する場合は、プロジェクトマニフェストに目的のアクティビティに、次の内容がほぼ含まれる新しいインテントフィルターを追加します。



 <intent-filter> <action android:name="android.intent.action.VIEW" /> <data android:mimeType="application/my-mime" /> (mime type,   ) <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
      
      







この場合、通知をクリックすると、アクティビティが起動され、ダウンロード識別子を含むインテントが既に転送されます。 たとえば、次のように取得できます。



意図の意図= getIntent();

文字列データ= intent.getDataString();



合計:


DownloadManagerサービスは、画像、メディアファイル、アーカイブなど、アプリケーションとは別にユーザーが関心を持つ可能性のある大きなファイルをダウンロードするのに便利です。他のアプリケーションもアップロードしたファイルにアクセスできることに注意してください。



これは、Androidアプリケーションのバックグラウンド作業を実装するためのすべてのパターンを網羅したということではありませんが、高い信頼性で、議論された方法は非常に広く普及していると言えます。この記事が、背景作業を最も正確かつ便利に設計するのに役立つことを願っています。



All Articles