私のプログラムはバックグラウンドスレッドでいくつかのネットワークアクティビティを行います。開始する前に、進行状況ダイアログがポップアップします。ダイアログはハンドラ上で閉じられます。ダイアログが表示されている間(およびバックグラウンドスレッドが進行中)に画面の向きが変わる場合を除いて、これはすべてうまくいきます。この時点で、アプリはクラッシュ、またはデッドロック、またはすべてのスレッドが終了するまでアプリがまったく動作しないという奇妙な段階に陥ります。
画面の向きの変更を適切に処理するにはどうすればよいですか。
以下のサンプルコードは、私の実際のプログラムの動作とほぼ一致しています。
public class MyAct extends Activity implements Runnable {
public ProgressDialog mProgress;
// UI has a button that when pressed calls send
public void send() {
mProgress = ProgressDialog.show(this, "Please wait",
"Please wait",
true, true);
Thread thread = new Thread(this);
thread.start();
}
public void run() {
Thread.sleep(10000);
Message msg = new Message();
mHandler.sendMessage(msg);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mProgress.dismiss();
}
};
}
スタック:
E/WindowManager( 244): Activity MyAct has leaked window com.Android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): Android.view.WindowLeaked: Activity MyAct has leaked window com.Android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager( 244): at Android.view.ViewRoot.<init>(ViewRoot.Java:178)
E/WindowManager( 244): at Android.view.WindowManagerImpl.addView(WindowManagerImpl.Java:147)
E/WindowManager( 244): at Android.view.WindowManagerImpl.addView(WindowManagerImpl.Java:90)
E/WindowManager( 244): at Android.view.Window$LocalWindowManager.addView(Window.Java:393)
E/WindowManager( 244): at Android.app.Dialog.show(Dialog.Java:212)
E/WindowManager( 244): at Android.app.ProgressDialog.show(ProgressDialog.Java:103)
E/WindowManager( 244): at Android.app.ProgressDialog.show(ProgressDialog.Java:91)
E/WindowManager( 244): at MyAct.send(MyAct.Java:294)
E/WindowManager( 244): at MyAct$4.onClick(MyAct.Java:174)
E/WindowManager( 244): at Android.view.View.performClick(View.Java:2129)
E/WindowManager( 244): at Android.view.View.onTouchEvent(View.Java:3543)
E/WindowManager( 244): at Android.widget.TextView.onTouchEvent(TextView.Java:4664)
E/WindowManager( 244): at Android.view.View.dispatchTouchEvent(View.Java:3198)
私はonSaveInstanceStateの進行状況ダイアログを消そうとしました、しかしそれはただのクラッシュを防ぐだけです。バックグラウンドスレッドはまだ進行中で、UIは部分的に描画された状態にあります。アプリが再び動作し始める前にアプリ全体を強制終了する必要があります。
向きを切り替えると、Androidは新しいビューを作成します。バックグラウンドスレッドが古いスレッドの状態を変更しようとしているため、おそらくクラッシュしています。 (バックグラウンドスレッドがUIスレッド上にないため、問題が発生している可能性もあります)
そのmHandlerを揮発性にして、向きが変わったら更新することをお勧めします。
編集: この StackOverflow post の中でDianne Hackborn(a.k.a. hackbod )が説明しているように、Googleのエンジニアはこのアプローチをお勧めしません。詳しくは このブログ記事 をご覧ください。
これをマニフェストのアクティビティ宣言に追加する必要があります。
Android:configChanges="orientation|screenSize"
だからそれはように見えます
<activity Android:label="@string/app_name"
Android:configChanges="orientation|screenSize|keyboardHidden"
Android:name=".your.package">
問題は、設定の変更が発生するとシステムがアクティビティを破棄することです。 ConfigurationChanges を参照してください。
それで設定ファイルにそれを入れることはシステムがあなたの活動を破壊するのを避けます。代わりにonConfigurationChanged(Configuration)
メソッドを呼び出します。
私は物事の 'Androidの方法'に準拠しているこれらの問題のための強固な解決策を思い付きました。私はIntentServiceパターンを使用して、長期にわたる操作をすべて行っています。
つまり、私のアクティビティはインテントをブロードキャストし、IntentServiceが作業を行い、データをDBに保存してから、スティッキーインテントをブロードキャストします。スティッキ部分は重要です。ユーザーが作業を開始してからIntentServiceからのリアルタイムブロードキャストを見逃した後にActivityが一時停止した場合でも、応答して呼び出し側のActivityからデータを取得することができます。 ProgressDialog
sはonSaveInstanceState()
と非常にうまくこのパターンを扱うことができます。
基本的には、保存されたインスタンスバンドルで進行状況ダイアログが実行されているというフラグを保存する必要があります。 進行状況ダイアログオブジェクトを保存しないでください。これはアクティビティ全体をリークするためです。進行状況ダイアログへの永続的なハンドルを保持するために、アプリケーションオブジェクトの弱い参照として保存します。方向を変更したり、アクティビティを一時停止して(電話、ユーザーが自宅に戻ったなど)、再開した場合は、古いダイアログを閉じて、新しく作成したアクティビティに新しいダイアログを再作成します。
無期限の進行状況ダイアログでは、これは簡単です。プログレスバースタイルの場合は、最後に確認された進捗状況をバンドルに含め、進捗状況を追跡するためにアクティビティでローカルに使用している情報をすべて含める必要があります。進捗状況を復元する際には、この情報を使用して進捗バーを以前と同じ状態に再生成し、現在の状態に基づいて更新します。
要約すると、onSaveInstanceState()
の賢明な使用と相まって、長時間実行されるタスクをIntentServiceに入れることで、ダイアログを効率的に追跡し、アクティビティライフサイクルイベント全体にわたって復元することができます。 Activityコードの関連ビットは以下の通りです。あなたのBroadcastReceiverにスティッキインテントを適切に処理するためのロジックも必要ですが、それはこの範囲を超えています。
public void doSignIn(View view) {
waiting=true;
AppClass app=(AppClass) getApplication();
String logingon=getString(R.string.signon);
app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
...
}
@Override
protected void onSaveInstanceState(Bundle saveState) {
super.onSaveInstanceState(saveState);
saveState.putBoolean("waiting",waiting);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null) {
restoreProgress(savedInstanceState);
}
...
}
private void restoreProgress(Bundle savedInstanceState) {
waiting=savedInstanceState.getBoolean("waiting");
if (waiting) {
AppClass app=(AppClass) getApplication();
ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
refresher.dismiss();
String logingon=getString(R.string.signon);
app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
}
}
私は同じ問題に出会った。私の活動はURLからいくつかのデータを解析する必要があり、それは遅いです。だから私はそうするためにスレッドを作成し、そして進行状況ダイアログを表示します。完了したら、スレッドにHandler
を介してUIスレッドにメッセージをポストさせます。 Handler.handleMessage
では、スレッドからデータオブジェクト(今すぐ準備)を取得し、それをUIに追加します。だからあなたの例に非常に似ています。
多くの試行錯誤の末、解決策を見つけたようです。少なくとも今、私はスレッドが終了する前後にいつでも画面を回転させることができます。すべてのテストで、ダイアログは適切に閉じられており、すべての動作は予想どおりです。
私がしたことを以下に示します。目標は、私のデータモデル(mDataObject
)を埋めて、それをUIに追加することです。何時でも画面を回転させても問題ありません。
class MyActivity {
private MyDataObject mDataObject = null;
private static MyThread mParserThread = null; // static, or make it singleton
OnCreate() {
...
Object retained = this.getLastNonConfigurationInstance();
if(retained != null) {
// data is already completely obtained before config change
// by my previous self.
// no need to create thread or show dialog at all
mDataObject = (MyDataObject) retained;
populateUI();
} else if(mParserThread != null && mParserThread.isAlive()){
// note: mParserThread is a static member or singleton object.
// config changed during parsing in previous instance. swap handler
// then wait for it to finish.
mParserThread.setHandler(new MyHandler());
} else {
// no data and no thread. likely initial run
// create thread, show dialog
mParserThread = new MyThread(..., new MyHandler());
mParserThread.start();
showDialog(DIALOG_PROGRESS);
}
}
// http://Android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
public Object onRetainNonConfigurationInstance() {
// my future self can get this without re-downloading
// if it's already ready.
return mDataObject;
}
// use Activity.showDialog instead of ProgressDialog.show
// so the dialog can be automatically managed across config change
@Override
protected Dialog onCreateDialog(int id) {
// show progress dialog here
}
// inner class of MyActivity
private class MyHandler extends Handler {
public void handleMessage(msg) {
mDataObject = mParserThread.getDataObject();
populateUI();
dismissDialog(DIALOG_PROGRESS);
}
}
}
class MyThread extends Thread {
Handler mHandler;
MyDataObject mDataObject;
// constructor with handler param
public MyHandler(..., Handler h) {
...
mHandler = h;
}
public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller
public void run() {
mDataObject = new MyDataObject();
// do the lengthy task to fill mDataObject with data
lengthyTask(mDataObject);
// done. notify activity
mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
}
}
それが私のために働くものです。これがAndroidによって設計された「正しい」方法であるかどうかはわかりません - 彼らはこの「画面の回転中にアクティビティを破棄/再作成する」ことで実際に作業が簡単になると主張します。
あなたが私のコードに問題があるかどうか私に知らせてください。上記のように私は副作用があるかどうか本当に知りません。
私の解決策は、自分のProgressDialog
を取得するためにMyProgressDialog
クラスを拡張することでした。Dialog
を表示する前に方向をロックし、Dialog
が却下されたときにロック解除するためにshow()
およびdismiss()
メソッドを再定義しました。そのため、Dialog
が表示され、デバイスの向きが変わると、画面の向きはdismiss()
が呼び出されるまで変わりません。その後、screen-orientationはsensor-values/device-orientationに従って変わります。
これが私のコードです:
public class MyProgressDialog extends ProgressDialog {
private Context mContext;
public MyProgressDialog(Context context) {
super(context);
mContext = context;
}
public MyProgressDialog(Context context, int theme) {
super(context, theme);
mContext = context;
}
public void show() {
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
else
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
super.show();
}
public void dismiss() {
super.dismiss();
((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
}
当初認識されていた問題は、コードが画面の向きの変更に耐えられないことです。どうやらこれは、(onDestroyを呼び出して)UIフレームワークに実行させるのではなく、プログラムに画面の向きの変更自体を処理させることで「解決」されたようです。
根本的な問題がプログラムがonDestroy()に耐えられないということであれば、それから受け入れられる解決策は深刻な他の問題と脆弱性をプログラムに残す単なる回避策です。 Androidのフレームワークでは、あなたの活動はあなたの管理外の事情によりほぼいつでも破壊される危険があると明確に述べています。そのため、画面の向きの変更だけでなく、何らかの理由で、アクティビティがonDestroy()およびそれに続くonCreate()に耐えることができなければなりません。
OPの問題を解決するために画面の向きの変更を自分で処理することを受け入れる場合は、onDestroy()の他の原因が同じエラーにならないことを確認する必要があります。できますか?そうでなければ、「受け入れられた」答えが本当に良い答えであるかどうか私は疑問に思うでしょう。
私はこれと同じ問題に直面しました、そして私はProgressDialogを使うことを忘れない解決策を思いつきました、そして私はより速い結果を得ます。
私がしたのはProgressBarを含むレイアウトを作成することでした。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent">
<ProgressBar
Android:id="@+id/progressImage"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_centerInParent="true"
/>
</RelativeLayout>
それからonCreateメソッドで以下を行います。
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.progress);
}
次に、長いタスクをスレッドで実行し、それが完了したら、Runnableを使用してコンテンツビューをこのアクティビティに使用する実際のレイアウトに設定します。
例えば:
mHandler.post(new Runnable(){
public void run() {
setContentView(R.layout.my_layout);
}
});
これが私がしたことであり、ProgressDialogを表示するよりも速く実行され、それほど邪魔にならず、私の意見ではより良く見えることがわかりました。
ただし、ProgressDialogを使用したい場合は、この答えはあなたのためではありません。
私はこのローテーションの問題に対処するための私のアプローチを貢献するつもりです。 OPはAsyncTask
を使用していないので、これはOPには関係がないかもしれませんが、他の人はそれが便利だと思うかもしれません。それはとても簡単ですが、それは私のために仕事をするようです:
私はAsyncTask
と呼ばれるネストされたBackgroundLoginTask
クラスによるログインアクティビティを持っています。
私のBackgroundLoginTask
では、ProgressDialog
のdismissを呼び出したときにnullチェックを追加する以外は、普通のことではありません。
@Override
protected void onPostExecute(Boolean result)
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
[...]
}
これは、Activity
が表示されていないのにバックグラウンドタスクが終了し、そのため進行状況ダイアログがonPause()
メソッドによって既に閉じられている場合を処理するためです。
次に、親のActivity
クラスで、AsyncTask
クラスとProgressDialog
クラスへのグローバル静的ハンドルを作成します(AsyncTask
はネストされているので、これらの変数にアクセスできます)。
private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;
これには2つの目的があります。まず、私のActivity
が、ポストローテーションされた新しいアクティビティからでも常にAsyncTask
オブジェクトにアクセスできるようになります。次に、ローテーションの後でもBackgroundLoginTask
がProgressDialog
にアクセスして却下できるようにします。
次に、これをonPause()
に追加します。これにより、Activity
がフォアグラウンドを離れると進捗ダイアログが消えます(その醜い「強制終了」クラッシュを防ぎます)。
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
最後に、私のonResume()
メソッドには以下のものがあります。
if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
}
これはDialog
が作り直された後にActivity
が再び現れることを可能にします。
これがクラス全体です:
public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;
private Controller cont;
// This is the app entry point.
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (CredentialsAvailableAndValidated())
{
//Go to main menu and don't run rest of onCreate method.
gotoMainMenu();
return;
}
setContentView(R.layout.login);
populateStoredCredentials();
}
//Save current progress to options when app is leaving foreground
@Override
public void onPause()
{
super.onPause();
saveCredentialsToPreferences(false);
//Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
if (pleaseWaitDialog != null)
pleaseWaitDialog.dismiss();
}
@Override
public void onResume()
{
super.onResume();
if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
{
if (pleaseWaitDialog != null)
pleaseWaitDialog.show();
}
}
/**
* Go to main menu, finishing this activity
*/
private void gotoMainMenu()
{
startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
finish();
}
/**
*
* @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
*/
private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
{
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
SharedPreferences.Editor prefEditor = settings.edit();
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
prefEditor.putString(USERNAME, usernameText.getText().toString());
prefEditor.putString(PASSWORD, pswText.getText().toString());
if (setValidatedBooleanTrue)
prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
prefEditor.commit();
}
/**
* Checks if user is already signed in
*/
private boolean CredentialsAvailableAndValidated() {
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
MODE_PRIVATE);
if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
return true;
else
return false;
}
//Populate stored credentials, if any available
private void populateStoredCredentials()
{
SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
MODE_PRIVATE);
settings.getString(USERNAME, "");
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
usernameText.setText(settings.getString(USERNAME, ""));
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
pswText.setText(settings.getString(PASSWORD, ""));
}
/**
* Validate credentials in a seperate thread, displaying a progress circle in the meantime
* If successful, save credentials in preferences and proceed to main menu activity
* If not, display an error message
*/
public void loginButtonClick(View view)
{
if (phoneIsOnline())
{
EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
EditText pswText = (EditText) findViewById(R.id.editTextPassword);
//Call background task worker with username and password params
backgroundLoginTask = new BackgroundLoginTask();
backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
}
else
{
//Display toast informing of no internet access
String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
/**
*
* Takes two params: username and password
*
*/
public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
{
private Exception e = null;
@Override
protected void onPreExecute()
{
cont = Controller.getInstance();
//Show progress dialog
String pleaseWait = getResources().getString(R.string.pleaseWait);
String commWithServer = getResources().getString(R.string.communicatingWithServer);
if (pleaseWaitDialog == null)
pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);
}
@Override
protected Boolean doInBackground(Object... params)
{
try {
//Returns true if credentials were valid. False if not. Exception if server could not be reached.
return cont.validateCredentials((String)params[0], (String)params[1]);
} catch (Exception e) {
this.e=e;
return false;
}
}
/**
* result is passed from doInBackground. Indicates whether credentials were validated.
*/
@Override
protected void onPostExecute(Boolean result)
{
//Hide progress dialog and handle exceptions
//Progress dialog may be null if rotation has been switched
if (pleaseWaitDialog != null)
{
pleaseWaitDialog.dismiss();
pleaseWaitDialog = null;
}
if (e != null)
{
//Show toast with exception text
String networkError = getResources().getString(R.string.serverErrorException);
Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
toast.show();
}
else
{
if (result == true)
{
saveCredentialsToPreferences(true);
gotoMainMenu();
}
else
{
String toastText = getResources().getString(R.string.invalidCredentialsEntered);
Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
toast.show();
}
}
}
}
}
私は決してベテランのAndroid開発者ではないので、コメントしてください。
私はまだ解決していないことを発見しました。バックグラウンドタスクが進行しているかどうかを知っているカスタムアプリケーションオブジェクトを使用することができます。これは、方向の変更時に破棄されて再作成されるアクティビティでこれを実行するのではなくです。これについて こちら でブログに書いています。
私はこのようにしました:
package com.palewar;
import Android.app.Activity;
import Android.app.ProgressDialog;
import Android.os.Bundle;
import Android.os.Handler;
import Android.os.Message;
public class ThreadActivity extends Activity {
static ProgressDialog dialog;
private Thread downloadThread;
final static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
dialog.dismiss();
}
};
protected void onDestroy() {
super.onDestroy();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
downloadThread = (Thread) getLastNonConfigurationInstance();
if (downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in...", false);
}
dialog = ProgressDialog.show(ThreadActivity.this, "",
"Signing in ...", false);
downloadThread = new MyThread();
downloadThread.start();
// processThread();
}
// Save the thread
@Override
public Object onRetainNonConfigurationInstance() {
return downloadThread;
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
// Simulate a slow network
try {
new Thread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
} finally {
}
}
}
}
あなたは試してみることもできますし、それがあなたのためであるかどうか知らせてください。
長いタスクを別のクラスに移動します。主観的観察者パターンとしてそれを実装する。アクティビティが作成されたときはいつでも登録時および終了時にタスククラスの登録を解除します。タスククラスはAsyncTaskを使用できます。
コツは、通常通りonPreExecute/onPostExecuteの間にAsyncTask内でダイアログを表示/終了することです。ただし、向きを変更する場合は、アクティビティ内でダイアログの新しいインスタンスを作成/表示し、その参照をタスクに渡します。
public class MainActivity extends Activity {
private Button mButton;
private MyTask mTask = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MyTask task = (MyTask) getLastNonConfigurationInstance();
if(task != null){
mTask = task;
mTask.mContext = this;
mTask.mDialog = ProgressDialog.show(this, "", "", true);
}
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
mTask = new MyTask(MainActivity.this);
mTask.execute();
}
});
}
@Override
public Object onRetainNonConfigurationInstance() {
String str = "null";
if(mTask != null){
str = mTask.toString();
mTask.mDialog.dismiss();
}
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
return mTask;
}
private class MyTask extends AsyncTask<Void, Void, Void>{
private ProgressDialog mDialog;
private MainActivity mContext;
public MyTask(MainActivity context){
super();
mContext = context;
}
protected void onPreExecute() {
mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
}
protected void onPostExecute(Void result) {
mContext.mTask = null;
mDialog.dismiss();
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(5000);
return null;
}
}
}
画面の向きが変わるとアクティビティが破棄されることを許可する実装を持っていますが、それでも再現されたアクティビティのダイアログは正常に破棄されます。私は...NonConfigurationInstance
を使用してバックグラウンドタスクを再作成されたアクティビティに添付します。通常のAndroidフレームワークはダイアログ自体の再作成を処理します。そこでは何も変更されません。
私はAsyncTaskをサブクラス化して「所有する」アクティビティのフィールドとこの所有者を更新するメソッドを追加しました。
class MyBackgroundTask extends AsyncTask<...> {
MyBackgroundTask (Activity a, ...) {
super();
this.ownerActivity = a;
}
public void attach(Activity a) {
ownerActivity = a;
}
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
ownerActivity.dismissDialog(DIALOG_PROGRESS);
}
...
}
私のアクティビティクラスで、私は '所有されている' backgroundtaskを参照するフィールドbackgroundTask
を追加し、そして私はonRetainNonConfigurationInstance
とgetLastNonConfigurationInstance
を使ってこのフィールドを更新します。
class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
...
if (getLastNonConfigurationInstance() != null) {
backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
backgroundTask.attach(this);
}
}
void startBackgroundTask() {
backgroundTask = new MyBackgroundTask(this, ...);
showDialog(DIALOG_PROGRESS);
backgroundTask.execute(...);
}
public Object onRetainNonConfigurationInstance() {
if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
return backgroundTask;
return null;
}
...
}
さらなる改善のための提案:
backgroundTask
参照を消去して、それに関連するメモリまたはその他のリソースを解放します。ownerActivity
参照を消去します。BackgroundTask
インターフェースやコレクションを作成して、異なるタイプのタスクを同じ所有アクティビティーから実行できるようにします。jfelectronの解決策を実装しようとしたのは、これらの問題に対する「堅実な解決策であり、「Androidの方法」に準拠しているためです。 「しかし、言及したすべての要素を調べてまとめるには時間がかかりました。このわずかに異なるものに終わった、そして私はもっとエレガントな、ここに掲載されている解決策の全体を考える。
アクティビティから起動されたIntentServiceを使用して、長期実行タスクを別のスレッドで実行します。サービスは、ダイアログを更新するアクティビティにスティッキーブロードキャストインテントを起動します。 ActivityはshowDialog()、onCreateDialog()、およびonPrepareDialog()を使用して、永続データをアプリケーションオブジェクトまたはsavedInstanceStateバンドルに渡す必要をなくします。アプリケーションがどのように中断されても、これは機能するはずです。
活動クラス:
public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button) this.findViewById(R.id.test_button);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
buttonClick();
}
});
}
private void buttonClick(){
clearPriorBroadcast();
showDialog(PROGRESS_DIALOG);
Intent svc = new Intent(this, MyService.class);
startService(svc);
}
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
mProgressDialog = new ProgressDialog(TesterActivity.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setMax(MyService.MAX_COUNTER);
mProgressDialog.setMessage("Processing...");
return mProgressDialog;
default:
return null;
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case PROGRESS_DIALOG:
// setup a broadcast receiver to receive update events from the long running process
IntentFilter filter = new IntentFilter();
filter.addAction(MyService.BG_PROCESS_INTENT);
registerReceiver(new MyBroadcastReceiver(), filter);
break;
}
}
public class MyBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(MyService.KEY_COUNTER)){
int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
mProgressDialog.setProgress(count);
if (count >= MyService.MAX_COUNTER){
dismissDialog(PROGRESS_DIALOG);
}
}
}
}
/*
* Sticky broadcasts persist and any prior broadcast will trigger in the
* broadcast receiver as soon as it is registered.
* To clear any prior broadcast this code sends a blank broadcast to clear
* the last sticky broadcast.
* This broadcast has no extras it will be ignored in the broadcast receiver
* setup in onPrepareDialog()
*/
private void clearPriorBroadcast(){
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
sendStickyBroadcast(broadcastIntent);
}}
IntentServiceクラス:
public class MyService extends IntentService {
public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;
public MyService() {
super("");
}
@Override
protected void onHandleIntent(Intent intent) {
for (int i = 0; i <= MAX_COUNTER; i++) {
Log.e("Service Example", " " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(BG_PROCESS_INTENT);
broadcastIntent.putExtra(KEY_COUNTER, i);
sendStickyBroadcast(broadcastIntent);
}
}}
マニフェストファイルのエントリ:
申請セクションの前:
uses-permission Android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission Android:name="Android.permission.BROADCAST_STICKY"
申請部内
service Android:name=".MyService"
バックグラウンド Service
を作成すると、すべての面倒な作業(tcp要求/応答、非整列化)が行われ、View
およびActivity
が破棄され、ウィンドウをリークしたりデータを失うことなく再作成できます。これは、Androidが推奨する動作を可能にします。これは、 各構成の変更ごとにActivityを破棄する _(例:各方向の変更ごと)です。
もう少し複雑ですが、サーバー要求、データの前後処理などを呼び出すための最善の方法です。
Service
を使用して各要求をサーバーにキューイングすることもできます。そのため、これらの処理を簡単かつ効率的に行うことができます。
開発者ガイドには完全な Services
に関する章 があります。
2つのレイアウトを管理している場合は、すべてのUIスレッドを終了させる必要があります。
AsynTaskを使用すると、現在のアクティビティの.cancel()
メソッド内でonDestroy()
メソッドを簡単に呼び出すことができます。
@Override
protected void onDestroy (){
removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
if (loginTask != null){
if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
loginTask.cancel(true); //cancel AsyncTask
}
super.onDestroy();
}
AsyncTaskについては、 の「タスクのキャンセル」のセクション を参照してください。
更新:実行状態にある場合にのみ取り消すことができるため、状況をチェックする条件が追加されました。 AsyncTaskは1回しか実行できないことにも注意してください。
これが私の提案した解決策です:
void showProgressDialog()
メソッドをフラグメントアクティビティリスナインタフェースに追加することができます。私はAndroidの新鮮な人です、そして、私はこれを試みました、そして、それはうまくいきました。
public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
ProgressDialog progressDialog = new ProgressDialog(Login.this);
int ranSucess=0;
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
progressDialog.setTitle("");
progressDialog.isIndeterminate();
progressDialog.setCancelable(false);
progressDialog.show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
@Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
return null;
}
@Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
progressDialog.dismiss();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
}
}
私はすべてを試してみました。実験に費やした日数。活動の回転を妨げたくはありませんでした。私のシナリオは次のとおりです。
問題は、画面を回転させると、本のすべての解決策が失敗することでした。 AsyncTaskクラスでさえ、これはこの状況に対処するための正しいAndroidの方法です。画面を回転させると、開始スレッドが使用していた現在のコンテキストは消え、表示されているダイアログと混同します。コードに追加したトリックの数(実行中のスレッドへの新しいコンテキストの受け渡し、ローテーションによるスレッド状態の保持など)に関係なく、問題は常にダイアログでした。最後のコードの複雑さは常に莫大であり、うまくいかない可能性があるものも常にありました。
私のために働いた唯一の解決策はActivity/Dialogトリックでした。それはシンプルで天才です、そしてそれはすべて回転証明です:
ダイアログを作成して表示するように依頼する代わりに、Android:theme = "@ Android:style/Theme.Dialog"を使用してマニフェストに設定されているActivityを作成します。それで、それはただ対話のように見えます。
ShowDialog(DIALOG_ID)をstartActivityForResult(yourActivityDialog、yourCode)に置き換えます。
呼び出し側のActivityでonActivityResultを使用して、実行中のスレッドからの結果(エラーも含む)を取得し、UIを更新します。
'ActivityDialog'で、長いタスクを実行するにはスレッドまたはAsyncTaskを使用し、画面を回転させるときの "ダイアログ"状態を保存するにはonRetainNonConfigurationInstanceを使用します。
これは速く、うまくいきます。私はまだ他のタスクのためにダイアログを使い、画面上に一定のダイアログを必要としない何かのためにAsyncTaskを使います。しかし、このシナリオでは、私はいつもActivity/Dialogパターンに行きます。
そして、私は試したことはありませんでしたが、呼び出し側のActivityが回転できるようにしながら、スレッドの実行中にそのActivity/Dialogの回転をブロックして処理速度を上げることさえも可能です。
最近では、これらのタイプの問題を処理するはるかに明確な方法があります。典型的なアプローチは次のとおりです。
バックグラウンドプロセスであるものはすべて、保持されたFragment
(Fragment.setRetainInstance()
でこれを設定します。これは、保持する任意のデータが保持される「永続的なデータストレージ」になります。イベント、このFragment
は、FragmentManager.findFragmentByTag()
呼び出しを介して元の状態で引き続きアクセスできます(作成するとき、IDではなくタグを与える必要があります) View
)に接続されていないためです。
これを正しく行う方法と、それが最良のオプションである理由については、 ランタイム変更の処理 開発ガイドを参照してください。
リンクプロセスをreverseする必要があります。現時点では、バックグラウンドプロセスはView
にアタッチされます。代わりに、View
がバックグラウンドプロセスにアタッチされます。もっと理にかなっていますか? View
のアクションはバックグラウンドプロセスに依存しますが、バックグラウンドプロセスはView
に依存しません。これは、リンクを標準のListener
インターフェイスに変更することを意味します。プロセス(クラスが何であれ-AsyncTask
、Runnable
など)がOnProcessFinishedListener
を定義しているとしましょう。プロセスが完了すると、リスナーが存在する場合は呼び出します。
この answer は、カスタムリスナーの実行方法に関する簡潔な説明です。
ここで、現在のView
構造が何であれ、バックグラウンドタスクとのインターフェイスについて心配する必要があります。向きの変更を適切に処理している場合(常に推奨されるconfigChanges
ハックではありません)、システムによってDialog
が再作成されます。これは重要です。方向の変更時に、Dialog
のすべてのライフサイクルメソッドが呼び出されることを意味します。したがって、これらのメソッドのいずれか(onCreateDialog
は通常適切な場所です)では、次のような呼び出しを行うことができます。
DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
public void onProcessFinished() {
dismiss();
}
});
}
個々の実装でリスナーを最適に設定する場所を決定するには、 Fragment lifecycle を参照してください。
これは、この質問で尋ねられた一般的な問題に対する堅牢で完全なソリューションを提供する一般的なアプローチです。おそらく、個々のシナリオに応じて、この回答にはいくつかの小さな部分が欠けていますが、これは通常、方向変更イベントを適切に処理するための最も正しいアプローチです。
これはどういうわけかサイドバーに出てきた非常に古い質問です。
アクティビティがフォアグラウンドにある間にバックグラウンドタスクが存続する必要があるだけの場合、「新しい」解決策はバックグラウンドスレッド(または、できればAsyncTask
)を保持フラグメントでホストすることですこの 開発者ガイド および 多数のQ&A に記載されているように。
設定が変更されたためにアクティビティが破棄されても保持されているフラグメントは存続しますが、バックグラウンドまたはバックスタックでアクティビティが破棄されても NOT は残りません。したがって、 isChangingConfigurations()
がonPause()
でfalseの場合、バックグラウンドタスクは中断されます。
私は同じ状況に直面しました。私がしたことは、アプリケーション全体で私のプログレスダイアログのインスタンスを1つだけ取得することでした。
まず、1つのインスタンスのみを取得するためのDialogSingletonクラスを作成しました(Singletonパターン)。
public class DialogSingleton
{
private static Dialog dialog;
private static final Object mLock = new Object();
private static DialogSingleton instance;
private DialogSingleton()
{
}
public static DialogSingleton GetInstance()
{
synchronized (mLock)
{
if(instance == null)
{
instance = new DialogSingleton();
}
return instance;
}
}
public void DialogShow(Context context, String title)
{
if(!((Activity)context).isFinishing())
{
dialog = new ProgressDialog(context, 2);
dialog.setCanceledOnTouchOutside(false);
dialog.setTitle(title);
dialog.show();
}
}
public void DialogDismiss(Context context)
{
if(!((Activity)context).isFinishing() && dialog.isShowing())
{
dialog.dismiss();
}
}
}
このクラスで示すように、進行状況ダイアログを属性として持っています。進捗ダイアログを表示する必要があるたびに、一意のインスタンスを取得して新しいProgressDialogを作成します。
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
バックグラウンドタスクが終了したら、もう一度一意のインスタンスを呼び出してダイアログを閉じます。
DialogSingleton.GetInstance().DialogDismiss(this);
バックグラウンドタスクのステータスを共有設定に保存します。画面を回転させるときに、このアクティビティに対してタスクを実行しているかどうかを確認します。(onCreate)
if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).
バックグラウンドタスクの実行を開始すると、
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");
DialogSingleton.GetInstance().DialogShow(this, "My title here!");
バックグラウンドタスクの実行が終了したら
preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");
DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);
助けになれば幸いです。
向きが変わったときにスレッドを処理するための解決策が見つかりました。あなたのアクティビティ/フラグメントへの静的な参照を保持し、UIに作用する前にそれがnullかどうかを確認することができます。 try catchも使うことをお勧めします。
public class DashListFragment extends Fragment {
private static DashListFragment ACTIVE_INSTANCE;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ACTIVE_INSTANCE = this;
new Handler().postDelayed(new Runnable() {
public void run() {
try {
if (ACTIVE_INSTANCE != null) {
setAdapter(); // this method do something on ui or use context
}
}
catch (Exception e) {}
}
}, 1500l);
}
@Override
public void onDestroy() {
super.onDestroy();
ACTIVE_INSTANCE = null;
}
}
本当であるにはあまりにも「迅速で汚い」と思われるので、欠陥を指摘してください。しかし私がうまくいったのは….
私のAsyncTaskのonPostExecuteメソッドの中で、私は単純に(空のcatchで)try/catchブロックで進行状況ダイアログのための '.dismiss'をラップし、そして単に発生した例外を無視しました。実行するのは間違っているように見えますが、悪影響を与えることはないようです(少なくとも、後で実行している処理では、長時間実行されたクエリの結果をExtraとして渡して別のアクティビティを開始します)。
最も単純で最も柔軟な解決策は、 ProgressBar への静的参照と共に AsyncTask を使用することです。これは、方向転換の問題に対するカプセル化された、したがって再利用可能な解決策を提供する。このソリューションは、インターネットでのダウンロード、 サービスとの通信 、ファイルシステムのスキャンなど、さまざまな非同期タスクに適しています。このソリューションは、複数のAndroidバージョンと電話モデルで十分にテストされています。完全なデモはここで見つけることができます ここで 特に興味を持っている DownloadFile.Java
概念の例として以下を提示します
public class SimpleAsync extends AsyncTask<String, Integer, String> {
private static ProgressDialog mProgressDialog = null;
private final Context mContext;
public SimpleAsync(Context context) {
mContext = context;
if ( mProgressDialog != null ) {
onPreExecute();
}
}
@Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog( mContext );
mProgressDialog.show();
}
@Override
protected void onPostExecute(String result) {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
mProgressDialog.setProgress( progress[0] );
}
@Override
protected String doInBackground(String... sUrl) {
// Do some work here
publishProgress(1);
return null;
}
public void dismiss() {
if ( mProgressDialog != null ) {
mProgressDialog.dismiss();
}
}
}
Androidアクティビティでの使い方は簡単です
public class MainActivity extends Activity {
DemoServiceClient mClient = null;
DownloadFile mDownloadFile = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
mDownloadFile = new DownloadFile( this );
Button downloadButton = (Button) findViewById( R.id.download_file_button );
downloadButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View view) {
mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
}
});
}
@Override
public void onPause() {
super.onPause();
mDownloadFile.dismiss();
}
}
ダイアログの向きの変更イベントに関連するアクティビティ参照を検出するのに苦労している場合、この方法は非常に効果的です。私はこれを使用します。なぜなら私は複数の異なるアクティビティに表示できる独自のダイアログクラスを持っているので、どのアクティビティに表示されているのかわからないからです。カスタムダイアログは必要ありません(私のように)。ただし、その特定のビューを使用して向きの変化を検出できるように、カスタムコンテンツビューが必要です。これが私の例です:
public class MyContentView extends View{
public MyContentView(Context context){
super(context);
}
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
//DO SOMETHING HERE!! :D
}
}
Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context)); //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();
ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context)); //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();