꿈꾸는 시스템 디자이너

AppWidget에서 ProgressDialog 사용법 본문

Development/Android

AppWidget에서 ProgressDialog 사용법

독행소년 2013. 9. 10. 14:23

앱위젯상에서 ProgressDialog를 표시할 필요가 많으나 정작 앱위젯에서는 ProgressDialog를 직접 실행 시킬 수 없다. 이를 해결하기 위해서는 ProgressDialog를 가지는 Activity를 실행시켜서 동일 효과를 주는 방법을 이용한다. 이를 위해선 부가적으로 activity를 투명하게 처리해야 한다. 우선 원하는 기능은 아래의 그림과 같다. 위젯이 동작하는 바탕화면에서 버튼을 클릭하면 ProgressDialog가 나타나는 것이다.



우선 앱위젯에 의해 실행될 ProgressDialog를 가지는 Activity를 투명하게 처리하기 위해서는, 메니페스트파일에 해당 activity를 투명으로 처리한다.


android:theme="@android:style/Theme.Translucent.NoTitleBar"


코드를 설명하기 앞서, 본 포스트에서 설명하는 ProgressDialog는 앱위젯의 요청에 의해 생성되는 activity에 의해 표시되므로, 앱위젯은 activity로 intent를 전달해야 한다. 이렇게 실행된 ProgressDialog는 정해진 시간 후에 자동으로 종료되어야 한다. 또한 ProgressDialog의 실행 중간이라도 앱위젯의 명령에 의해 즉시 종료되는 일종의 인터럽트도 지원해야 한다. 이른 위해 위 그림에서 표시된 두 버튼 중, "ProgressDialog 생성" 버튼을 클릭하면 10초가 표시되는 ProgressDialog를 실행하고, "ProgressDialog 삭제 요청" 버튼을 클릭하면 activity에 Interrupt를 전달하여 실행 중인 ProgressDialog를 종료해야 한다.

다만, ProgressDialog가 실행 중인 상태에서는 삭제 요청 버튼을 클릭할 수 없으므로, 삭제 요청 버튼을 클릭하면 4초 후에 activity로 Interrupt를 전달하도록 구현한다. 즉 ProgressDialog가 Interrupt에 의해 실행 도중 종료는 효과를 보기 위해서는 삭제 요청 버튼을 먼저 클릭한 후 "ProgressDialog 생성"버튼을 클릭하는 방식으로 구현하였다.


앱위젯 개발에 대한 이해가 부족하다면, 이전 포스트들을 참조하기 바란다.


<MainWidget.class>

package com.example.widgettest2;

import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.ProgressDialog;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.widget.RemoteViews;
import android.widget.Toast;

public class MainWidget extends AppWidgetProvider{

	private RemoteViews remoteViews;
	
	// ProgressDialog를 표시하기 위해 activity 호출에 사용할 action
	public static String SHOW_DIALOG_ACTION = "com.example.widgettest2.show_dialog_action";
	// activity에 TIME_OUT_ACTION을 늦게 전송하기 위해 delay 처리를 위한 action
	public static String MAKE_DELAY_ACTION = "com.example.widgettest2.set_time_out";
	// ProgressDialog를 제거하기 위해 activity 호출에 사용할 action
	public static String TIME_OUT_ACTION = "com.example.widgettest2.time_out";
	
	private Context mContext;
	
	@Override
	public void onReceive(Context context, Intent intent) {
		super.onReceive(context, intent);

		// context 저장, 핸들러에서 Intent 생성에 사용
		mContext = context;
		
		// Action 저장
		String action = intent.getAction();

		/**********************************************************************************
		 * Broadcast 요청 처리부
		 **********************************************************************************/
		
		// MAKE_DELAY_ACTION 수신 
		if(action.equals(MAKE_DELAY_ACTION)){
			// 핸들러로 delayedMessage를 전달
			mHandler.sendEmptyMessageDelayed(100, 4000);
		}
		// TIME_OUT_ACTION 수신
		if(action.equals(TIME_OUT_ACTION)){
			// 실행중인 activity에 ProgressDialog를 종료하라는 명령을 전달할 intent 생성 및 action 설정
			Intent intentToSendInterrupt = new Intent(context,ProgressDialogActivity.class);
			intentToSendInterrupt.setAction(TIME_OUT_ACTION);
			try {
				// 본 PendingIntent는 activity에서 수실할 것이므로, getActivity()와 send() 사용
				PendingIntent.getActivity(context, 0, intentToSendInterrupt, 0).send();
			} catch (CanceledException e) {
				e.printStackTrace();
			}
		}

		
		/***********************************************************************************
		 * GUI 구성부
		 ***********************************************************************************/
		
		// RemoteViews 객체 생성
		remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget_main);

		// 목적: Dialog 생성 버튼 클릭시, activity 실행
		// 실행할 activity를 이용해 intent 생성
		Intent intentToStartActivity = new Intent(context,ProgressDialogActivity.class);
		// intent에 action 설정, 이 action은 activity(ProgressDialogActivity)로 전달됨
		intentToStartActivity.setAction(SHOW_DIALOG_ACTION);
		// 생성한 intent를 이용해 PendingIntent 생성, 새로운 activity로 전달할 것이므로 getActivity 사용
		PendingIntent pIntentToStartActivity = PendingIntent.getActivity(context,R.id.btCallDialog, intentToStartActivity, 0);
		// 버튼에 PendingIntent 연결
		remoteViews.setOnClickPendingIntent(R.id.btCallDialog, pIntentToStartActivity);
		
		// 목적: 화면에 표시할 ProgressDialog에 Interrupt를 전달해서 종료시킬 목적 
		// intent 생성 및 action 설정, 이 action은 본 widget의 브로트캐스트리시버(onReceive)로 수신
		Intent intentToMakeDelayedIntent = new Intent(context,MainWidget.class);
		intentToMakeDelayedIntent.setAction(MAKE_DELAY_ACTION);
		// 본 위젯으로 전달할 것이므로 getBroadcast 사용
		PendingIntent pIntentToMakeDelayedIntent = PendingIntent.getBroadcast(context, R.id.btDismissDialog, intentToMakeDelayedIntent, 0);
		// 버튼에 PendingIntent 연결
		remoteViews.setOnClickPendingIntent(R.id.btDismissDialog, pIntentToMakeDelayedIntent);
	
		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
		ComponentName cpName = new ComponentName(context,MainWidget.class);
		appWidgetManager.updateAppWidget(cpName, remoteViews);
	}
	
	
	public Handler mHandler = new Handler(){
		
		// onReceive()의 MAKE_DELAY_ACTION 처리부에 의해 호출됨
		public void handleMessage(Message msg){
			// TIME_OUT_ACTION 값으로 인텐트 생성, 이 인텐트는 본 위젯의 브로드캐스트리시버로 수신됨
			Intent intent = new Intent(mContext,MainWidget.class);
			intent.setAction(TIME_OUT_ACTION);
			try {
				// 생성한 intent로 PendingIntent생성
				// 본 브로드캐스트리시버로 전달할 것이므로 getBroadcast() 사용
				// PendingIntent를 바로 실행하기 위해 send() 사용
				PendingIntent.getBroadcast(mContext, 0, intent, 0).send();
			} catch (CanceledException e) {
				e.printStackTrace();
			}
		}
		
	};
}

ProgressDialog 생성 버튼을 클릭하면, ProgressDialogActivity.class가 실행되면서 SHOW_DIALOG_ACTION이 전달된다. 

ProgressDialog 삭제 요청 버튼을 클릭하면, MAKE_DELAY_ACTION이 브로드캐스트리시버로 전달되고(line 46), 브로드캐스트리시버(onReceive())에서는 4초간의 딜레이를 가지는 메시지를 핸들러로 전달한다(line 48). 핸들러에서는 4초후에 해당 메시지를 수신하고(line 99), TIME_OUT_ACTION 값으로 다시 브로드캐스트리시버를 호출한다(line 107). 최종적으로 브로드캐스트리시버가 TIME_OUT_ACTION을 수신하면(line 51), 동일 action값(TIME_OUT_ACTION)을 가지는 인텐트를 생성하여 실행중인 activity로 Interrupt 메시지를 전달한다(line 57).

즉 activity는 SHOW_DIALOG_ACTION를 가지는 intent에 의해 ProgressDialog를 실행하고, TIME_OUT_ACTION를 가지는 intent에 의해 ProgressDialog를 종료한다.


<ProgressDialogActivity.class>

package com.example.widgettest2;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.WindowManager;
import android.widget.Toast;

public class ProgressDialogActivity extends Activity {
	
	private boolean isRunning = false;
	private ProgressDialog pDialog;
	private int DONE = 100;
	private String action = null;
	
	@Override
	protected void onResume() {
		super.onResume();
		//Toast.makeText(getBaseContext(), "action= "+action+"\nisRunning= "+isRunning, 0).show();
		
		// action값을 확인하여
		// ProgressDialog를 실행 요청이면
		if(action.equals(MainWidget.SHOW_DIALOG_ACTION)){
			// ProgressDialog 실행
			showDialog();
			// 화면상에 ProgressDialog가 실행되고 있음을 명시
			isRunning = true;
		}
		// ProgressDialog의 종료 요청이고, 실제 ProgressDialog가 실행중이면
		else if(action.equals(MainWidget.TIME_OUT_ACTION)&&isRunning == true){
			// ProgressDialog 종료
			hideDialog();
		}
		// 사전 ProgressDialog 요청 없이, 종료 요청이 수신된 경우, 위젯으로 돌아감
		else
			finish();
	}

	// intentToSendInterrupt에 의해 호출
	// (중요!!!) intentToSendInterrupt 수신 시, 본 activity가 실행중이지 않았다면 onCreate()가 호출됨 
	@Override
	protected void onNewIntent(Intent intent) {
		// argument intent로부터 action값을 읽어 저장, getIntent().getAction()으로 사용하면 안됨
		action = intent.getAction();
		//Toast.makeText(getBaseContext(), "onNewIntent", 0).show();
		super.onNewIntent(intent);
	}

	// intentToStartActivity에 의해 호출
	// (중요!!!) intentToSendInterrupt 수신 시, 본 activity가 실행중이지 않았다면 onCreate()가 호출됨
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// 위젯상에 ProgressDialog만 표시되는 것 처럼 하기 위해, activity를 투명으로 처리
		this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
		// action값을 읽어 저장
		action = getIntent().getAction();
		//Toast.makeText(getBaseContext(), "onCreate", 0).show();
	}
	
	// ProgressDialog를 실행
	public void showDialog(){
		pDialog = new ProgressDialog(this);
		pDialog.setTitle("Title");
		pDialog.setMessage("Message");
		pDialog.setIndeterminate(true);
		pDialog.setCancelable(true);
		pDialog.show();
		// ProgressDialog를 실행한 후 10초후 ProgressDialog 종료를 위한 메시지 전달
		mHandler.sendEmptyMessageDelayed(DONE, 10000);
	}
	
	// ProgressDialog를 종료하고 activity도 종료, 위젯으로 돌아감
	public void hideDialog(){
		pDialog.dismiss();
		isRunning = false;
		finish();
	}
	
	public Handler mHandler = new Handler(){
		// ProgressDialog 실행 10초후에 호출됨
		public void handleMessage(Message msg){
			if(msg.what == DONE){
				// ProgressDialog 실행도중 별도의 Interrupt intent가 수신되지 않으면,
				if(isRunning = true){
					// ProgressDialog 종료
					hideDialog();
				}
			}
		}
	};
} 

위 코드를 설명하기 앞서, 위의 activity는 실행 도중에 interrupt를 수신할 수 있어야 한다. 이를 위해서 앱위젯에서 인텐트를 통해 activity를 호출할 때 동일(단일) activity로 인텐트가 수신되도록 하기 위해서 본 activity를 singleTop으로 설정해야 한다. 이를 위해 메니페스트 파일에 아래의 내용을 추가한다.


android:launchMode="singleTop"

위와 같이 선언하면, 모든 인텐트는 하나의 activity로 수신되게 된다. 이 때 첫번째 수신된 인텐트는 onCreate()로 수신되고, 두번째 인텐트부터는 onNewIntent()로 수신된다. 또한 getIntent().getAction()은 처음 수신된 인텐트의 액션을 반환한다. 그러므로 onNewIntent()에서 수신한 액션값을 구하기 위해서는 intent.getAction()을 이용해햐함을 주의한다.

또한 본 activity을 투명처리하기 위해서는 메니페시트파일에 설정한 것 외에도 onCreate()에서도 투명처리를 해줘야 한다(line 58).


Comments