Tag Archives: Widget

[Android] 간단하게 만드는 주기적으로 업데이트하는 위젯 만들기

안드로이드의 위젯은 주기적으로 onUpdate를 호출 할 수 있습니다. 다음과 같이 설정할 경우 30분마다 한번씩 위젯을 갱신할 수 있습니다.

android:updatePeriodMillis=”1800000″

하지만 위의 방법은 구글측에서 제한을 두어 최소 30분 이하의 갱신을 수행할 수 없습니다. 이하의 값을 설정할 경우 자동으로 30분 갱신으로 등록이 됩니다. 0을 등록할 경우 슬립모드에 들어갔다가 화면이 켜질경우에 업데이트를 수행하게 됩니다.

이때문에 더 짧은 주기의 업데이트를 수행하기 위해서는 AlarmManager를 활용하여 수동으로 위젯을 업데이트 하는 방법밖에 없어 보입니다. 다행이도 안드로이드에는 다음과 같은 위젯을 업데이트 하기 위한 인텐트 필터가 존재합니다.

android.appwidget.action.APPWIDGET_UPDATE

저 인텐트를 주기적으로 호출해 주기만 하면 문제가 해결될 수 있을것 같군요. 위젯을 사용하기 위한 방법까지 포함해서 하나하나 정리해 보도록 하겠습니다.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="kr.pe.theeye.widget"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-sdk android:minSdkVersion="7" />

  <application
  android:icon="@drawable/icon"
  android:label="@string/app_name">

  <receiver android:name="HelloWidgetProvider" android:label="위젯테스트">
    <intent-filter>
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data
      android:name="android.appwidget.provider"
      android:resource="@xml/hellowidgetproviderinfo" />
  </receiver>

  </application>
</manifest>

위에서 중요한 부분은 android:name에 AppWidgetProvider를 상속받은 클래스의 클래스명을 적어줘야 한다는 것입니다. android:label에는 위젯을 추가할때에 표시될 위젯의 이름이 표시됩니다. 메타데이터에는 위젯의 기본적인 정보가 적혀있는 XML의 위치를 정의 합니다.

hellowidgetproviderinfo.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/hellowidget_layout"
android:minWidth="220dp"
android:minHeight="72dp"/>

여기서는 위젯의 크기라던가 레이아웃XML파일의 위치를 정의할 수 있습니다. 레이아웃의 크기는 하나의 셀에 74픽셀을 사용하게 됩니다. 하지만 [여기]를 참고해 보면 위젯의 사이즈는 총 크기에서 2픽셀을 빼라고((number of cells * 74) – 2) 되어있군요.

hellowidget_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:background="#fff">
<TextView 
            android:id="@+id/widgettext"
   android:layout_width="fill_parent" 
   android:layout_height="wrap_content"
   android:gravity="center"
   android:text="@string/hello"/>
</LinearLayout>

위의 디자인이 제가 테스트로 사용하기 위한 위젯 디자인입니다. 단순하게 글짜만 바꿀 수 있도록 TextView하나를 사용하였습니다.

HelloWidgetProvider.java

/**
 * 위젯의 상태를 주기적으로 갱신하는 예제
 * 
 * @author Eye
 * @since 2011.04.01
 */
public class HelloWidgetProvider extends AppWidgetProvider
{
  private static final String TAG = "HelloWidgetProvider";
  private static final int WIDGET_UPDATE_INTERVAL = 5000;
  private static PendingIntent mSender;
  private static AlarmManager mManager;

  /* (non-Javadoc)
   * @see android.appwidget.AppWidgetProvider#onReceive(android.content.Context, android.content.Intent)
   */
  @Override
  public void onReceive(Context context, Intent intent)
  {
    super.onReceive(context, intent);

    String action = intent.getAction();
    // 위젯 업데이트 인텐트를 수신했을 때
    if(action.equals("android.appwidget.action.APPWIDGET_UPDATE"))
    {
      Log.w(TAG, "android.appwidget.action.APPWIDGET_UPDATE");
      removePreviousAlarm();

      long firstTime = System.currentTimeMillis() + WIDGET_UPDATE_INTERVAL;
      mSender = PendingIntent.getBroadcast(context, 0, intent, 0);
      mManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
      mManager.set(AlarmManager.RTC, firstTime, mSender);
    }
    // 위젯 제거 인텐트를 수신했을 때
    else if(action.equals("android.appwidget.action.APPWIDGET_DISABLED"))
    {
      Log.w(TAG, "android.appwidget.action.APPWIDGET_DISABLED");
      removePreviousAlarm();
    }
  }

  /* (non-Javadoc)
   * @see android.appwidget.AppWidgetProvider#onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[])
   */
  @Override
  public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
  {
    // 현재 클래스로 등록된 모든 위젯의 리스트를 가져옴
    appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, getClass()));
    super.onUpdate(context, appWidgetManager, appWidgetIds);

    final int N = appWidgetIds.length;
    for(int i = 0 ; i < N ; i++)
    {
      int appWidgetId = appWidgetIds[i];
      updateAppWidget(context, appWidgetManager, appWidgetId);

      Toast.makeText(context, "onUpdate(): [" + String.valueOf(i) + "] " + String.valueOf(appWidgetId), Toast.LENGTH_SHORT).show();
    }
  }

  /**
   * 위젯의 형태를 업데이트합니다.
   * 
   * @param context 컨텍스트
   * @param appWidgetManager 위젯 메니저
   * @param appWidgetId 업데이트할 위젯 아이디
   */
  public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId)
  {
    Date now = new Date();
    RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.hellowidget_layout);
    updateViews.setTextViewText(R.id.widgettext, "[" + String.valueOf(appWidgetId) + "]" + now.toLocaleString());
    appWidgetManager.updateAppWidget(appWidgetId, updateViews);
  }

  /**
   * 예약되어있는 알람을 취소합니다.
   */
  public void removePreviousAlarm()
  {
    if(mManager != null && mSender != null)
    {
      mSender.cancel();
      mManager.cancel(mSender);
    }
  }
}

여기서 참고해볼점은 위젯이 등록될 때 알람을 등록하고 위젯을 삭제할때 알람을 취소시킨다는것입니다. 또한 다음의 코드를 이용하여 위젯의 숫자와 상관없이 동일하게 갱신할 수 있도록 할 수 있습니다.

int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, getClass()));

위의 코드를 사용하면 현재의 WidgetProvider를 사용한 모든 홈스크린에 등록된 위젯들의 ID를 얻어올 수 있습니다.
사용자 삽입 이미지
[샘플코드 다운로드]

[iPhone/QR Code] XZing QR코드 리더 사용하기

QR코드 리더를 구현하기 위해 이리저리 알아보던중에 정말 멋지다 못해 거의 완벽한 라이브러리를 발견하였습니다. XZing이라는 것인데요. 다음과 같이 다양한 바코드를 읽을 수 있습니다.


UPC-A and UPC-E
EAN-8 and EAN-13
Code 39
Code 93
Code 128
QR Code
ITF
Codabar
RSS-14 (all variants)
Data Matrix
PDF 417 (‘alpha’ quality)

또한 이 라이브러리의 강점은 정말 다양한 플랫폼에서 동일하게 동작한다는 것인데요, 다음과 같은 다양한 언어/OS에서 동작할 수 있습니다.


javame: JavaME client
csharp: Partial C# port
cpp: Partial C++ port
rim: RIM/Blackberry-specific client build
iphone: iPhone client + port to Objective C / C++ (QR code only)
bug: Client for BugLabs’s BUG
jruby: Ruby wrapper
actionscript: partial port to Actionscript


정말 안되는게 없네요. 하지만 우선 아이폰 기반에서 어떻게 사용하는지에 대해 적어보도록 하겠습니다. 중요한 소스코드는 iphone/안에 모두 모여있습니다. iphone/ZXingWidget/프로젝트가 범용적으로 사용할 수 있는 뷰컨트롤러 형태의 라이브러리 프로젝트입니다. 이것을 실제로 사용한 샘플을 제작해 둔 것이 iphone/ScanTest/입니다. ScanTest를 면밀히 분석해 보면 어떻게 ZXingWidget라이브러리를 사용하는지 알 수 있지만 과정을 적어보도록 하겠습니다.

결과적으로 마지막에는 ScanTest소스를 보여드리겠지만 ZXingWidget프로젝트를 가지고 내가 새로운 프로젝트를 생성하여 개발한다고 가정해 보겠습니다.

1. ZXingWidget 디렉토리 안에 있는 ZXingWidget.xcodeproj 파일을 새로운 프로젝트의 Groups and Files 사이드바로 드래그하여 넣어줍니다. 이때에 파일을 복사할꺼냐고 물어보는데 복사는 하지 않습니다. Reference TypeRelative to Project로 선택해 줍니다.

사용자 삽입 이미지
2. 추가한 파일을 선택(1)하면 오른쪽에 libZXingWidget.a 파일이 리스팅됩니다. 그 옆(2)에 체크해 줍니다.

사용자 삽입 이미지3. 이제 Targets(1)이하의 프로젝트를 선택하여 Info(2)를 눌러 설정으로 들어갑니다.

사용자 삽입 이미지
4. General(1)탭안의 Direct Dependencies+(2)를 눌러 ZXingWidget(3)을 추가해 줍니다.

사용자 삽입 이미지
5. 이제 프로젝트의 설정창에 들어가 Header Search Paths(3)설정을 찾아 ZXingWidget라이브러리 프로젝트 안의 Classes의 상대 경로(5)를 추가해줍니다. 이때에 Recursive에 체크를 하셔야 합니다.

6. 이제 설정은 모두 끝났습니다. 다음의 헤더 파일이 핵심이 됨을 잊지 마세요.

[code]#import “ZXingWidgetController.h” // QR코드 리더 뷰 컨트롤러
#import “QRCodeReader.h” // QR코드 파싱을 위한 리더 등록용[/code]
7. 마지막으로 ScanTest 프로젝트의 샘플 코드를 잠깐 보겠습니다.

[code]- (IBAction)scanPressed:(id)sender
{
  // QR코드 리더 전용 뷰 컨트롤러 생성
  ZXingWidgetController *widController =
  [[ZXingWidgetController alloc] initWithDelegate:self showCancel:YES OneDMode:NO];

  // QR코드 분석기 생성
  QRCodeReader* qrcodeReader = [[QRCodeReader alloc] init];
  NSSet *readers = [[NSSet alloc ] initWithObjects:qrcodeReader,nil];
  [qrcodeReader release];
  widController.readers = readers;
  [readers release];

  // 인식 성공시에 플레이될 음악
  NSBundle *mainBundle = [NSBundle mainBundle];
  widController.soundToPlay =
  [NSURL fileURLWithPath:
    [mainBundle pathForResource:@”beep-beep” ofType:@”aiff”] isDirectory:NO];

  // 뷰를 보여준다
  [self presentModalViewController:widController animated:YES];
  [widController release];
}


#pragma mark –
#pragma mark ZXingDelegateMethods


– (void)zxingController:(ZXingWidgetController*)controller didScanResult:(NSString *)result {
  // 정상적으로 QR코드를 읽었을때 – 해독된 문자열이 result에 담겨있다.
  self.resultsToDisplay = result;
  if (self.isViewLoaded)
  {
    [resultsView setText:resultsToDisplay];
    [resultsView setNeedsDisplay];
  }
  [self dismissModalViewControllerAnimated:NO];
}


– (void)zxingControllerDidCancel:(ZXingWidgetController*)controller
{
  // 사용자가 취소했을 경우
  [self dismissModalViewControllerAnimated:YES];
}[/code]


참고로 ZXingWidget 라이브러리의 BaseSDK가 정상적으로 설정이 되어있다면 ScanTest에서 정상적으로 빌드가 될 때 ZXingWidget의 Product 폴더에 .a파일이 생성됨을 볼 수 있습니다. 이 파일만을 복사해다가 프로젝트안에 추가하면 위의 복잡한 설정없이 바로 사용 가능합니다.