[안드로이드] 펜딩인텐트 (Pending Intent)

by Blogger 하얀쿠아
2017. 5. 23. 01:06 소프트웨어 Note/Android

안드로이드의 펜딩인텐트 (Pending Intent)


펜딩인텐트 (Pending Intent) 는 인텐트의 일종이다. 

그러면 일반 인텐트와의 차이점은 무엇인지 알아보는 것부터 시작해보자.

컴포넌트에서 다른 컴포넌트에게 작업을 요청하는 인텐트를 사전에 생성시키고 만든다는 점과 "특정 시점"에 자신이 아닌 다른 컴포넌트들이 펜딩인텐트를 사용하여 다른 컴포넌트에게 작업을 요청시키는 데 사용된다는 점이 차이점이다.

수행시킬 작업 및 인텐트(실행의도)와 및 그것을 수행하는 주체를 지정하기 위한 정보를 명시 할 수 있는 기능의 클래스라고 보면 된다. 

이해하기 쉽게 말하자면, 아래와 같이 하고 싶을때 사용할 수 있는 것이다.





A한테 이 B인텐트를 C시점에 실행하라고 해. 지금은 실행하지 말고.

이 클래스의 인스턴스는 getActivity (Context, int, Intent, int), getActivities (Context, int, Intent [], int), getBroadcast (Context, int, Intent, int) 및  getService(Context, int, Intent, int) 가 반환 하는 객체를 다른 응용 프로그램으로 전달 할 수 있으므로 앱 개발자가 명시하는 작업을 수행 할 수 있다.

PendingIntent를 다른 응용 프로그램에 제공하면 다른 응용 프로그램이 자신과 동일한 권한과 ID로 지정된 것처럼 작업을 수행 할 수있는 권한이 부여된다. 따라서 PendingIntent를 작성하는 방법에주의해야한다. 예를 들어, 제공하는 기본적인 인텐트는 컴포넌트 이름이 자신이 가진 컴포넌트들 중 하나를 명시적으로 지정해야 하며,  궁극적으로 그곳으로 보내지는 것을 보장해야한다.

사용되는 몇가지 사례

펜딩인텐트를 사용하는 대표적인 몇가지 예가 있다.

  • 사용자가 Notification을 통해 특정한 동작을 할 때, 실행되는 인텐트를 생성함 (NotificationManager가 인텐트를 실행)
  • 사용자가 AppWidget을 통해 특정한 동작을 할 때,  실행되는 인텐트를 생성함 (홈 스크린이 인텐트를 실행)
  • 미래의 특정 시점에 실행되는 인텐트를 선언함 (안드로이드의 AlarmManager가 인텐트를 실행)

안드로이드 앱을 구현할 때, 인터넷으로부터 파일을 다운로드 하는 로직은 대부분 서비스에서 이루어지도록 구성한다. 
그런데 서비스는 액티비티와 달리 화면에 나타나지 않는다. 따라서 서비스는 다운로드의 진행중이라는 사실 및 진행정도를 화면 상단에 위치한 노티피케이션의 상태바(Status Bar)를 통해서 표현한다. 다운로드가 현재 진행 중이라는 상황을 표시하는 아이콘 등으로 말이다. 그리고 다운로드가 완료된 후에는 아이콘으로 다운로드 완료의 상태를 보여주게 된다.

사용자가 상태바의 아이콘을 확인하고 안드로이드 화면의 상태바를 누르면서 나타나는 바를 잡아 아래로 끌어당기면 나타나는 화면을 노티피케이션 리스트(Notification List)  또는 확장 메시지라 한다. 그리고 만약 서비스가 이 Notification List에 '다운로드 완료' 를 표시를 추가해놓았고, 사용자가 이것을 클릭하면, 노티피케이션은 사전에 서비스에서 작성한 펜딩인텐트를 사용하여 다운로드된 파일을 읽을 수 있는 애플리케이션을 호출하고 다운로드 완료된 파일을 호출된 애플리케이션으로 재생(혹은 보여줌) 하게 된다. 

펜딩인텐트는 안드로이드 App의 각각의 컴포넌트들이 펜딩인텐트를 생성할 수 있도록 다음과 같은 메서드를 제공한다.

아래의 메서드들을 통해 펜딩인텐트를 사용하고자 하는 컴포넌트 유형을 지정해야 한다는 뜻이다.


  • getActivity(Context, int, Intent, int) -> Activity를 시작하는 인텐트를 생성함
  • getBroadcast(Context, int, Intent, int) -> BroadcastReceiver를 시작하는 인텐트를 생성함
  • getService(Context, int, Intent, int)  -> Service를 시작하는 인텐트를 생성함

잘 보면, 3개의 메서드는 모두 Context를 필요로 한다. 이 Context는 '현재 App의 Context'이다.


예제 코드

실제로 PendingIntent를 활용해 Notification 발생시키는 예제 코드를 살펴보겠다.

이 예제코드를 따라가면 아래 화면과 같은 앱을 얻게될 것이다.





예제코드를 살펴보자.

총 5개의 파일(AndroidManifest.xml, MainActivity.java, NotificationSomething.java, activity_main.xml, notification_something.xml)  을 보게 될 것이다.

하나의 android application project를 생성하고, 아래와 같이 파일을 구성하자.

layout 2개와 java코드 2개를 만들면 된다.




AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.pendingintent"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
        <activity android:name=".NotificationSomething">
        </activity>
    </application>

</manifest>


앱이 실행되면 처음 보여지는 메인 Activity를 구성하는 layout이다. 1개의 버튼을 가지고 있다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tistory.webnautes.notification.MainActivity">
 
    <Button
        android:text="노티피케이션 발생시키기"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/button" />
</RelativeLayout>


앱이 실행되면 처음 보여지는 메인 Activity의 소스코드이다. 버튼을 눌렀을 때, NotificationSomethings( )라는 메서드를 호출하며, PendingIntent를 통해 Notification을 발생시키고 있다. Notification을 발생시키는 intent에서 putExtra( ) 메서드를 통해 몇가지 데이터를 넘기는 것을 주목하자. 특히 notificationId는 나중에 Notification을 목록에서 지울 때 사용한다.

MainActivity.java

package com.example.pendingintent;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.support.v4.app.NotificationCompat;


public class MainActivity extends Activity {
	Button button = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		 
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                NotificationSomethings();
            }
        });
	}

    public void NotificationSomethings() {
        Resources res = getResources();
        int notfi_id = 1;
 
        Intent notificationIntent = new Intent(this, NotificationSomething.class);
        notificationIntent.putExtra("notificationId", notfi_id); //전달할 값
        notificationIntent.putExtra("extraString", "Hello PendingIntent"); //전달할 값
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
 
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
 
        builder.setContentTitle("상태바 드래그시 보이는 타이틀")
                .setContentText("상태바 드래그시 보이는 서브타이틀")
                .setTicker("상태바 한줄 메시지")
                .setSmallIcon(R.drawable.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.ic_launcher))
                .setContentIntent(contentIntent)
                .setAutoCancel(true)
                .setWhen(System.currentTimeMillis())
                .setDefaults(Notification.DEFAULT_ALL);
 
        
 /*
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            builder.setCategory(Notification.CATEGORY_MESSAGE)
                    .setPriority(Notification.PRIORITY_HIGH)
                    .setVisibility(Notification.VISIBILITY_PUBLIC);
        }
 */
        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nm.notify(notfi_id, builder.build());
    }
}


첫 화면에서 발생한 Notification을 눌렀을 때 보여지는 Activity의 layout이다.

notification_something.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <TextView
        android:text="TextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout>


첫 화면에서 발생한 Notification을 눌렀을 때 보여지는 Activity의 소스코드이다.

NotificationSomething.java

package com.example.pendingintent;

import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.widget.TextView;

public class NotificationSomething extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.notification_something);
        CharSequence s = "전달 받은 값: ";
        int id=0;
        String extraData = "";
 
        Bundle extras = getIntent().getExtras();
        if (extras == null) {
            s = "error";
        }
        else {
            id = extras.getInt("notificationId");
            extraData = extras.getString("extraString");
        }
        TextView t = (TextView) findViewById(R.id.textView);
        s = s+" "+ extraData + "id:" + id;
        
        t.setText(s);
        NotificationManager nm =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 
        //노티피케이션 제거
        nm.cancel(id);
	}
}



관련 Reference Document 바로가기

https://developer.android.com/reference/android/app/PendingIntent.html

이 댓글을 비밀 댓글로

[안드로이드] 안드로이드 Wi-Fi를 앱에서 소스 코드(API)로 끄고 켜는 방법

by Blogger 하얀쿠아
2017. 5. 12. 00:47 소프트웨어 Note/Android

안드로이드 Wi-Fi를 코드(API)로 끄고 켜는 방법

종종 앱을 개발하다보면, Wi-Fi를 앱에서 프로그램으로 제어 하고 싶을 때가 있다.
안드로이드 장치의 Wi-Fi를 사용자의 터치 입력이 아니라, 자신이 개발중인 앱의 소스 코드 상에서, API호출을 하여 끄거나 켜는 방법을 알아보자.




우선, 아래와 같이 Wi-Fi 관련 권한들을 매니페스트 파일(AndroidManifest.xml)에 선언해야 한다.

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"></uses-permission>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>




이제 간단히 예제를 만들어 보겠다.

Enable 및 Disable 기능을하는 2개의 버튼을 가진 간단한 샘플 앱을 만들 것이다.


우선, 간단히 아래와 같이 xml을 통해 Layout을 만든다. 파일이름은 activity_main.xml 으로 했다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" 
    android:background="#DE6735">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="76dp"
        android:layout_marginTop="67dp"
        android:background="#123456"
        android:text="Enable Wifi"
        android:textColor="#fff" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/button1"
        android:layout_below="@+id/button1"
        android:text="Disable Wifi" />

</RelativeLayout>


그리고 AndroidManifest.xml 은 아래와 같이 했다.

uses-permission 부분을 신경써서 살펴보고 일치시키자.


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

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"></uses-permission>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
    <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.wifi.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



Main Activity 소스코드인 MainActivity.java 파일에서는 아래와 같이 2개의 버튼에 listener를 달고, 각 listener 메서드 안에서 WifiManager 인스턴스를 얻어서 켜거나 끄도록 했다.

enableButton과 disableButton 의 OnClickListener 부분을 신경써서 보자.

필요하다면, 이 부분만 발췌해서 바로 본인 코드에 적용하면 될 것이다.


package com.example.wifi;

import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
	Button enableButton,disableButton;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		enableButton=(Button)findViewById(R.id.button1);
		disableButton=(Button)findViewById(R.id.button2);
		
		enableButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v){
				WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
				wifi.setWifiEnabled(true);
			}
		});
		disableButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v){
				WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
				wifi.setWifiEnabled(false);
			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}
}


이번 포스팅에서는 Android API 를 통해 Wi-Fi를 끄거나 켜는 방법을 간단히 살펴보았다.

이 댓글을 비밀 댓글로

[안드로이드] 안드로이드 Activity

by Blogger 하얀쿠아
2017. 5. 11. 15:39 소프트웨어 Note/Android

안드로이드 액티비티란?

Activity는 일종의 애플리케이션 구성 요소로서, 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기 등의 일을 하기 위해 상호작용할 수 있는 화면을 제공한다.
액티비티마다 창이 하나씩 주어지며, 안드로이드 앱 개발자는 이곳에 UI(사용자 인터페이스)를 구현하게된다. 이 창은 일반적으로 화면을 가득 채우지만, 작은 창으로 만들어 다른 창 위에 띄울 수도 있다.

하나의 애플리케이션은 보통 여러 개의 액티비티가 느슨하게 서로 묶여 있는 형태로 구성된다. 통상 한 애플리케이션 내에서 하나의 액티비티가 "Main" 액티비티로 지정되며, 사용자가 이 애플리케이션을 처음 실행할 때 이 "Main"으로 지정된 액티비티가 사용자에게 최초로 보여지게 된다. 

 각각의 액티비티는 여러 가지 작업을 수행하기 위해 또 다른 액티비티를 시작할 수 있다. 새로운 액티비티가 시작될 때마다 이전 액티비티는 중단되지만 시스템은 해당 액티비티를 스택("백 스택")에 보존하게된다. 새로운 액티비티가 시작되면, 이전 액티비티는 백 스택으로 Push되며, 새로운 액티비티가 사용자 포커스를 갖게된다. 백 스택은 기본적인 "후입선출" 방식을 준수하므로, 사용자가 현재 액티비티를 끝내고 'Back'키를 누르면 해당 액티비티가 스택에서 Pop되고, 스택상에서는 지워지며, 이전 액티비티가 재개(resume)된다.

 하나의 액티비티가 다른 하나의 새로운 액티비티의 시작으로 인해 중단된 경우, 액티비티에 "상태 변경이 발생했다" 라고 말할 수 있다. 이러한 액티비티의 상태 변경은 액티비티의 수명 주기 콜백 메서드를 통해 통지된다. 액티비티가 시스템 액티비티를 생성, 중단, 재시작, 제거하는 등의 상태 변화로 인해 받을 수 있는 콜백 메서드는 여러 가지가 있다. 각 콜백은 상태 변화에 알맞은 특정 작업을 수행할 기회를 개발자에게 제공한다. 예를 들어 액티비티가 중단(stop)되면 네트워크 또는 데이터베이스 연결과 같은 자원들을 해제(release)해야 한다. 액티비티가 재개(resume)되면, 필요한 리소스를 다시 획득(acquire)하여 중단된 작업을 다시 시작할 수 있다. 이러한 상태 전환은 모두 액티비티 수명 주기의 일부이다.

이 포스팅에서 다양한 액티비티 상태 사이의 전환을 적절히 관리할 수 있도록 액티비티 수명 주기가 작동하는 방식을 자세히 논하는 등, 액티비티를 구축하고 사용하는 기본적 방법을 설명해보겠다.

액티비티 생성

액티비티를 생성하려면 Activity의 서브클래스(또는 이의 기존 서브클래스)를 생성해야 한다. 

그리고, 서브클래스에서는 액티비티 생성, 중단, 재개, 소멸 시기 등과 같은 수명 주기의 다양한 상태 사이를 액티비티가 전환 할 때마다 시스템이 호출해주는 콜백 메서드를 구현해야 한다. 

가장 중요한 콜백 메서드는 아래 두가지다. 일단 이것만 기억하고 시작해보자.

onCreate()
이 콜백메서드는 반드시 구현해야 한다. 액티비티를 생성할 때 안드로이드 시스템이 이것을 호출한다. 또한 구현부 최상단에서는 액티비티의 필수 구성 요소를 초기화해야 한다. 무엇보다도 중요한 점은, 바로 이 콜백에서 setContentView()를 호출해야 액티비티의 사용자 인터페이스 레이아웃을 화면에 뜨게 할 수 있다는 점이다. 즉, 이걸 구현하지 않으면 화면에 아무것도 보이지않는다.

onPause()
시스템이 이 메서드를 호출하는 것은 사용자가 액티비티를 떠난다는 첫 번째 신호다.(다만 이것이 항상 액티비티가 소멸 중이라는 뜻은 아님)
현재 사용자 세션을 넘어서 지속되어야 하는 변경 사항을 커밋하려면 보통 이곳에서 해야 한다.(사용자가 다시 이 액티비티 화면으로 돌아오지 않을 수 있기 때문이다. 안드로이드 폰 사용할 때, 어떤 앱을 한참 쓰다가 홈버튼 누르고, 그날 그 앱을 실행 하지 않는 경우를 상상하자. 사용자는 하룻밤을 자고나서 그앱을 실행할 수 있고, 심지어 한 일주일 뒤에 그앱을 실행할 수도 있다.)

아직 다루진 않았지만 여러 액티비티 사이에서 원활한 사용자 환경을 제공하고, 액티비티 중단이나 심지어 소멸을 초래할 수도 있는 예상치 못한 간섭을 처리하기 위해 사용해야 하는 다른 수명 주기 콜백 메서드도 여러 가지 있다.


사용자 인터페이스 구현

한 액티비티에 대한 사용자 인터페이스는 뷰 계층—즉, View 클래스에서 파생된 객체가 제공한다. 

각각의 뷰는 액티비티 창 안의 특정한 직사각형 공간을 제어하며 사용자 상호작용에 대응할 수 있다. 
예를 들면, 어떤 뷰는 사용자가 터치하면 작업을 시작하는 버튼일 수 있다.

Android는 레이아웃을 디자인하고 정리하는 데 사용할 수 있도록 여러 가지 뷰를 미리 만들어 제공한다. 

"위젯"이란 화면에 시각적(및 대화형) 요소를 제공하는 뷰이다. 예를 들어 버튼, 텍스트 필드, 체크박스나 그저 하나의 이미지일 수도 있다.

"레이아웃"은 선형 레이아웃, 격자형 레이아웃, 상대적 레이아웃과 같이 하위 레이아웃에 대해 독특한 레이아웃 모델을 제공하는 ViewGroup에서 파생된 뷰이다. 또한, View와 ViewGroup 클래스(또는 기존 서브클래스)의 아래로 내려가서 위젯과 레이아웃을 생성하고 이를 액티비티 레이아웃에 적용할 수 있다.

뷰를 사용하여 레이아웃을 정의하는 가장 보편적인 방식은 애플리케이션 리소스에 저장된 XML 레이아웃 파일을 사용하는 것이다. 이렇게 하면 액티비티의 동작을 정의하는 소스 코드와 별개로 사용자 인터페이스 디자인을 유지할 수 있다. setContentView()로 액티비티의 UI로서 레이아웃을 설정하고, 해당 레이아웃의 리소스 ID를 전달할 수 있다. 그러나 액티비티 코드에 새로운 View를 생성하고 새로운 View를 ViewGroup에 삽입하여 뷰 계층을 구축한 뒤 루트 ViewGroup을 setContentView()에 전달해도 해당 레이아웃을 사용할 수 있다.



액티비티 수명 주기 관리

콜백 메서드를 구현하여 액티비티의 수명 주기를 관리하는 것은 강력하고 유연한 애플리케이션 개발에 대단히 중요한 역할을 한다. 액티비티의 수명 주기는 다른 액티비티와의 관계, 액티비티의 작업과 백 스택 등에 직접적으로 영향을 받는다.

액티비티는 기본적으로 세 가지 상태로 존재할 수 있다.

재개됨(Resumed)
액티비티가 화면 포그라운드에 있고 사용자 포커스를 갖고 있다. (이 상태를 때로는 "실행 중"이라고 일컫기도 한다).

일시정지됨(Paused)
다른 액티비티가 포그라운드에 나와 있고 사용자의 시선을 집중시키고 있지만, 이 액티비티도 여전히 표시되어 있다. 다시 말해, 다른 액티비티가 이 액티비티 위에 표시되어 있으며 해당 액티비티는 부분적으로 투명하거나 전체 화면을 덮지 않는 상태라는 뜻이다. 일시정지된 액티비티는 완전히 살아있지만(Activity 객체가 메모리에 보관되어 있고, 모든 상태 및 멤버 정보를 유지하며, 창 관리자에 붙어 있는 상태로 유지됨), 메모리가 극히 부족한 경우에는 예외적으로 안드로이드 시스템 판단에 따라, 중단시킬 수 있다.

정지됨(Stopped)
이 액티비티가 다른 액티비티에 완전히 가려진 상태다. (액티비티가 이제 "백그라운드"에 위치함). 
중단된 액티비티도 여전히 살아 있기는 마찬가지이다. (Activity 객체가 메모리에 보관되어 있고, 모든 상태와 멤버 정보를 유지하시만 창 관리자에 붙어 있지 않음 ). 
그러나, 이는 더 이상 사용자에게 표시되지 않으며 다른 곳에 메모리가 필요한 상황이 되면 언제든지 안드로이드 시스템이 임의대로 종료시킬 수 있다.


액티비티가 일시정지 또는 중단된 상태이면, 시스템이 이를 메모리에서 삭제할 수 있음을 기억하자.
이러기 위해서는 종료를 요청하거나(finish() 메서드를 호출) 단순히 이 액티비티의 프로세스를 중단시키면 된다.
또한, 종료 또는 중단된 이후에 액티비티가 다시 열릴 때는 처음부터 다시 생성해야 한다는 점을 상기하자.


수명 주기 콜백 구현

위에서 설명한 바와 같이 액티비티가 여러 가지 상태를 오가며 전환되면, 이와 같은 내용이 여러 가지 콜백 메서드를 통해 통지된다.

콜백 메서드는 모두 hooking 가능한 시점이다.

액티비티 상태가 변경될 때마다 적절한 작업을 수행시킬 필요가 있다면, 개발자는 이를 직접 재정의할 수 있다.

다음의 기본 액티비티에는 기본 수명 주기 메서드가 각각 하나씩 포함되어 있다.

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The activity is being created.
    }
    @Override
    protected void onStart() {
        super.onStart();
        // The activity is about to become visible.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // The activity has become visible (it is now "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Another activity is taking focus (this activity is about to be "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        // The activity is no longer visible (it is now "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // The activity is about to be destroyed.
    }
}


 이와 같은 메서드를 모두 합쳐 '하나의 액티비티의 수명 주기 전체' 로 정의한다. 이러한 메서드를 구현함으로써 액티비티 수명 주기 내의 세 가지 중첩된 루프를 모니터할 수 있다.

 한 액티비티의 전체 수명은 onCreate() 호출과 onDestroy() 호출 사이를 말한다. 액티비티는 onCreate()에서 "전체" 상태(레이아웃 정의 등)의 설정을 수행한 다음 나머지 리소스를 모두 onDestroy()에 해제해 주어야 한다. 예를 들어, 액티비티에 네트워크에서 데이터를 다운로드하기 위해 배경에서 실행 중인 스레드가 있는 경우, 이는 해당 스레드를 onCreate()에서 생성한 다음 onDestroy()에서 그 스레드를 중단할 수 있어야 한다.

 한 액티비티의 가시적 수명은 onStart() 호출과 onStop() 호출 사이를 말한다. 이 기간 중에는 사용자가 액티비티를 화면에서 보고 이와 상호작용할 수 있다. 예컨대 onStop()이 호출되어 새 액티비티가 시작되면 이 액티비티는 더 이상 표시되지 않게 된다. 이와 같은 두 가지 메서드 중에서 사용자에게 액티비티를 표시하는 데 필요한 리소스를 유지하면 된다. 예를 들어, onStart()에서 BroadcastReceiver를 등록하고 UI에 영향을 미치는 변화를 모니터링하고 onStop()에서 등록을 해제하면 사용자는 여러분이 무엇을 표시하고 있는지 더 이상 볼 수 없게 된다. 시스템은 액티비티의 전체 수명 내내 onStart() 및 onStop()을 여러 번 호출할 수 있으며, 이때 액티비티는 사용자에게 표시되었다 숨겨지는 상태를 오가게 된다.

액티비티의 Foreground 수명은 onResume( ) 호출과 onPause( ) 호출 사이를 말한다. 이 기간 중에는 이 액티비티가 화면에서 다른 모든 액티비티 앞에 표시되며 사용자 입력도 여기에 집중된다. 액티비티는 포그라운드에 나타났다 숨겨지는 전환을 자주 반복할 수 있다. 예를 들어 , 기기가 절전 모드에 들어가거나 대화상자가 나타나면 onPause()가 호출된다. 이 상태는 자주 전환될 수 있으므로, 이 두 가지 메서드의 코드는 상당히 가벼워야 한다. 그래야 전환이 느려 사용자를 기다리게 하는 일을 피할 수 있다.


아래 그림은 액티비티가 상태 사이에서 취할 수 있는 이와 같은 루프와 경로를 나타낸 것이다. 직사각형은 콜백 메서드를 나타내며, 이 콜백 메서드에서 액티비티가 여러 상태 사이를 전환할 때 작업을 수행하도록 구현할 수 있다.


액티비티 수명 주기


이 댓글을 비밀 댓글로

[안드로이드] SDK manager 통한 업데이트후 ADT 실행 시 오류

by Blogger 하얀쿠아
2017. 3. 15. 23:02 소프트웨어 Note/Android

지금은 많은 안드로이드 어플리케이션 개발자들이 Eclipse + ADT 조합에서 Android Studio로 넘어갔을 것이라 예상한다.

하지만, 나는 아직도 Eclipse + ADT 조합을 이용한다.


그런데 어느순간, SDK manager를 통해 SDK를 업데이트 하고 나서부터 아래와 같이 ADT를 상위버전으로 업데이트 하라는 에러가 발생한다.



그냥 무시하고 써도 되겠거니.. 하고 Close하고 살펴봤는데, 이게 웬걸.

아무것도 할수가 없었다.

관련  패키지 로딩이 제대로 되지 않는 것으로 판단된다.


그래서 지시한 대로 [Help > Check for Updates]를 클릭하면 업데이트할 항목이 없다거나, 엉뚱한 것들만 표시된다. 

SubVersion 혹은 SVN 같은 것들.



한참 헤매다 보니, 아래와 같은 방법으로 ADT 업데이트가 가능했다.


[Help > Install New Software] 선택 후, Work with: 부분에 URL을 아래와 같이 google의 android eclipse 로 맞춰준다.


https://dl-ssl.google.com/android/eclipse/




[ADD] 버튼을 눌러주면 위와 같이 Android 관련 설치 가능한(업데이트 가능한) 패키지 목록이 보여진다.

모두 체크하고 [NEXT] 항목을 눌러 설치해주면 된다.

이제 ADT가 정상적으로 업데이트 되었고, 패키지 로딩에도 문제가 발생하지 않는것을 볼 수 있을 것이다.

이 댓글을 비밀 댓글로

[안드로이드] 동적으로 다음페이지를 로딩하는 ListView 구현

by Blogger 하얀쿠아
2012. 2. 26. 14:46 소프트웨어 Note/Android

동적으로 다음페이지를 로딩하는 ListView 구현


아이폰의 수많은 UITableView를 활용하는 어플리케이션을 보면 참 퀄리티 높게 잘 만든다는 생각이 드는 것 중에 한가지가 바로, 자동으로 리스트의 가장 아래에 도달하면, 알아서 다음페이지를 로딩하는 기능이 아닐까 싶다. 안드로이드에서도 요즘은 많은 어플리케이션이 해당 기능을 구현하고 있다. 안드로이드에서는 리스트뷰와 데이터간에 Adapter라는 디자인패턴을 활용하고 있어 아이폰의 그것과는 같은 기능이라도 구현하는 방식이 다르다.

안드로이드에서는 좀 더 적극적으로 Adapter를 활용하여 이 기능을 구현해야 한다. 어찌보면 조잡하고 어찌보면 더 쉽게 구현할 수 있다. 길게 이야기할것 없이 예제 코드를 살펴 보겠다.

 

public class DynamicListViewActivity extends Activity implements OnScrollListener
{
  private static final String LOG = "DynamicListViewActivity";
  private CustomAdapter mAdapter;
  private ListView mListView;
  private LayoutInflater mInflater;
  private ArrayList mRowList;
  private boolean mLockListView;
  
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // 멤버 변수 초기화
        mRowList = new ArrayList();
        mLockListView = true;
        
        // 어댑터와 리스트뷰 초기화
        mAdapter = new CustomAdapter(this, R.layout.row, mRowList);
        mListView = (ListView) findViewById(R.id.listView);
        
        // 푸터를 등록합니다. setAdapter 이전에 해야 합니다. 
        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mListView.addFooterView(mInflater.inflate(R.layout.footer, null));
        
        // 스크롤 리스너를 등록합니다. onScroll에 추가구현을 해줍니다.
        mListView.setOnScrollListener(this);
        mListView.setAdapter(mAdapter);
        
        // 데미데이터를 추가하기 위해 임의로 만든 메서드 호출
        addItems(50);
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem,
    int visibleItemCount, int totalItemCount)
  {
    // 현재 가장 처음에 보이는 셀번호와 보여지는 셀번호를 더한값이
    // 전체의 숫자와 동일해지면 가장 아래로 스크롤 되었다고 가정합니다.
    int count = totalItemCount - visibleItemCount;

    if(firstVisibleItem >= count && totalItemCount != 0
      && mLockListView == false)
    {
      Log.i(LOG, "Loading next items");
      addItems(50);
    }  
  }

  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState)
  {
  }
  
  /**
   * 임의의 방법으로 더미 아이템을 추가합니다.
   * 
   * @param size
   */
  private void addItems(final int size)
  {
    // 아이템을 추가하는 동안 중복 요청을 방지하기 위해 락을 걸어둡니다.
    mLockListView = true;
    
    Runnable run = new Runnable()
    {
      @Override
      public void run()
      {
        for(int i = 0 ; i < size ; i++)
        {
          mRowList.add("Item " + i);
        }
        
        // 모든 데이터를 로드하여 적용하였다면 어댑터에 알리고
        // 리스트뷰의 락을 해제합니다.
        mAdapter.notifyDataSetChanged();
        mLockListView = false;
      }
    };
    
    // 속도의 딜레이를 구현하기 위한 꼼수
    Handler handler = new Handler();
    handler.postDelayed(run, 5000);
  }
}


 여기서 주목할 부분은 onScroll 메서드다. 스크롤 동작이 발생 할 때마다 해당 메서드가 호출 되며, 위의 소스에서는 가장 마지막셀이 디스플레이 되었는지를 검사하게 된다. 마지막 셀이 나왔다면 현재 리스트가 Lock상태인지를 체크 한다. 여기서 쓰이는 멤버 변수가 mLockListView 이다.

해당 변수를 사용하여 리스트에 데이터가 변화하는 순간에는 스크롤 이벤트를 막아 이벤트의 중복 요청을 막게 됩니다. 위에서 Inflater를 활용하여 FooterView를 붙이는 과정이 있는데, 이것이 사용자에게 페이지 로딩중임을 알리게 되는 중요한 요소이다.



DynamicPageLoadingListView.zip

첨부된 예제 소스코드를 확인해보면 좋을 것이다.





이 댓글을 비밀 댓글로

[안드로이드] 안드로이드의 Process & Threads

by Blogger 하얀쿠아
2012. 2. 8. 17:09 소프트웨어 Note/Android

안드로이드의 Process & Threads

안드로이드의 프로세스(Process)와 스레드(Threads)에 대해서 공부해 보았다.

학교에서 배운 운영체제, 시스템 프로그래밍 등의 과목에서 배운 개념과 크게 다른 점은 없지만, 안드로이드의 프로세스와 스레드에는 분명 안드로이드 고유의 특성이 추가적으로 포함되어 있다.

다음 링크의 자료를 참조 하였다. 


시작하며

 안드로이드 어플리케이션 컴포넌트(components)가 실행하려 할 때, 이미 시작된(running) 다른 컴포넌트가 없다면, 시스템은 단일한 하나의 스레드에서 실행되도록 하는 어플리케이션 프로세스를 시작한다. 기본적으로, 같은 어플리케이션에서 실행되는 모든 컴포넌트들은 같은 프로세스와 (main thread 라고 불리는) 스레드에서 돌아간다. 만약 어플리케이션이 컴포넌트를 시작하려고 할 때, 이미 해당 어플리케이션의 프로세스가 돌아가고 있는 중이라면, 이제 새롭게 시작하는 어플리케이션 컴포넌트는 그 프로세스에서 시작되어 동일한 스레드를 이용하게 된다. 하지만 개발자는 필요에 따라, 어떤 컴포넌트들은 다른 프로세스나 스레드에서 돌아가도록 할 수도 있다.

 
* 안드로이드 컴포넌트 : activity, service, receiver, provider

 

프로세스(process)

앞에서도 설명했듯이 기본적으로 같은 어플리케이션에서 시작되는 모든 컴포넌트들은 같은 프로세스에서 돌아가고, 실제도 대부분의 어플리케이션은 이를 수정하지 않는다. 그러나, 가령 특정한 컴포넌트가 속한 프로세스를 관리하려 할 필요가 있을 수 있는데, 이를 manifest.xml 파일에서 관리 해 줄 수 있다.
 
 각 컴포넌트들의 manifest.xml 파일의 진입 포인트 - <activity> <service> <receiver> <provider> - 에서는 android:process 라는 속성을 지원한다. 이 속성을 사용하게 되면, 이 속성을 사용한 컴포넌트는, 이 속성에서 명시한 프로세스에서 돌아가게 된다. 그러므로 이 속성을 활용하면, 개발자가 필요에 따라 적절하게 컴포넌트들이 어떤 프로세스에서 돌아가야 하는지를 명시할 수 있고, 혹은 여러개의 컴포넌트들이 하나의 프로세스를 공유할 수도 있다. 또한, 다른 어플리케이션에서도 프로세스에 접근 가능하도록 할 수도 있다.
 
<application> 도 또한 android:process 속성을 가지는데, 해당 어플리케이션의 모든 컴포넌트들에게 기본값에 해당하는 프로세스를 지정할 때 쓰인다.
 
 안드로이드에서는 다른 프로세스를 실행하려는데 메모리가 부족할 때, 기존에 동작하고 있는 프로세스를 죽임으로써 메모리를 확보하려고 한다. 이 때 프로세스가 죽게되면, 그 프로세스에서 돌아가고 있던 컴포넌트들 역시 죽게 된다. 시스템에서 프로세스를 죽일 때는, 유저의 입장에서 덜 중요한 프로세스 부터 죽이게 된다. 예를 들어, 화면에 보여지는 컴포넌트를 가지고 있는 프로세스보다는 화면에 보이지 않는 컴포넌트를 가진 프로세스를 먼저 죽이게 된다. 결론은, 프로세스를 죽이는 기준은, 그 프로세스에서 돌아가고 있는 컴포넌트들의 상태에 의해 결정된다.

Process life-cycle
 안드로이드 시스템은 할 수만 있다면 최대한으로 어플리케이션 프로세스를 유지하려고 한다. 그러나, 메모리 공간은 한계가 있기 때문에, 메모리 공간이 부족하게 되면 어쩔 수 없이 프로세스를 종료시켜야 한다. 이 때 시스템에서 어떤 프로세스를 먼저 죽여야할 지 중요도를 결정짓게 되는데, 그 중요도라는 것이 앞에서 언급한대로, 프로세스에서 돌아가고 있는 컴포넌트들이 얼마나 중요한지에 따라 결정되는 것이다.
 
프로세스는 중요도에 따라 총 다섯가지 단계로 나누어 지며, 가장 덜 중요한 프로세스부터 죽게 된다.

 1) Foreground process
유저가 뭔가를 하고 있어서 지금 필요로 하는 프로세스가 여기에 해당한다. 구체적으로, 아래의 조건에 해당하는 프로세스들을 의미한다.
- 유저와 상호작용하고 있는 activity 를 호스팅한 프로세스 (액티비티의 onResume() 메소드가 호출된 상태)
- 유저와 상호작용하고 있는 activity에 바운드(bound) 된 서비스를 호스팅한 프로세스
- in the foreground 에서 돌아가고 있는 서비스를 호스팅한 프로세스 (서비스가 startForeground() 를 호출한 상태)
- 다음의 생명주기(lifecycle) 콜백 메소드 - onCreate(), onStart(), onDestroy() - 중에서 하나를 실행한 서비스를 호스팅한 프로세스
- onReceive() 메소드를 실행한 BroadcastReceiver 를 호스팅한 프로세스
 
2) Visible process
foreground components 를 가지고 있진 않지만, 여전히 화면에 보여지는 컴포넌트들을 가지는 프로세스가 이에 해당한다.
- foreground 에 있진 않지만 여전히 유저의 눈에 보이는 액티비티를 가지는 프로세스 (onPause() 까지 호출 된 상태)
- 눈에 보이거나 혹은 foreground 에 있는 액티비티에 바운딩(bound) 된 서비스를 호스팅한 프로세스
 
3) Service process
상위 두 개의 카테고리에 속하지 않으면서, startService() 메소드를 실행한 서비스(service) 를 동작하는 프로세스가 여기에 해당한다.  가령, 배경음악을 듣는다던지, 그런것들이 속한다.
 
4) Background process
눈에 보이지 않는 액티비티(onStop() 이 호출된 액티비티)를 가지고 있는 프로세스가 여기에 해당한다. 이들은 유저에게 직접적으로 영향을 끼치지 않기 때문에 시스템이 언제든지 삭제를 할 수 있다. background 프로세스들은 다양할 수 있기 때문에, 이들 프로세스들은 LRU(least recently used) 리스트에서 관리되며, 그래서 가장 최근에 보여진 액티비티를 가지는 프로세스가 가장 나중에 죽을 수 있도록 해준다.
 
5) Empty process
어떠한 활성화된 컴포넌트들도 가지고 있지 않은 프로세스이다. 그럼에도 불구하고 이런 종류의 프로세스를 유지하는 이유는, 캐시(cache) 를 활용하여 다음번 컴포넌트가 실행될 때 실행 시간을 빨리하기 위함이다.
 
안드로이드에서 프로세스의 중요도를 계산할 때는, 현재 프로세스에서 활성화된 컴포넌트의 중요도에 따라 결정짓는다. 예를 들어, 프로세스가 서비스 컴포넌트와 눈에 보이는 액티비티 컴포넌트를 가지고 있다면, 프로세스의 중요도는 눈에 보이는 액티비티의 중요도와 같아 진다. (만약, 서비스 컴포넌트의 중요도로 프로세스 중요도를 결정한다면, 이 프로세스가 눈에 보이는 액티비티를 가지고 있기 때문에, 함부로 죽으면 안됨에도 불구하고, 시스템에서는 서비스 컴포넌트 만큼의 중요도를 가지고 있다고 생각하고, 눈에 보이는 액비비티가 있는 상황에서 프로세스를 죽여버리는 불상사가 발생하게 될 것이다.)
 
또한 프로세스의 랭킹은 다른 프로세스와의 관계에 따라 결정된다. 만약 A 프로세스가 B 프로세스를 서브(serve) 하고 있다면, A 프로세스의 랭킹이 B 프로세스보다 낮을 수 없다.

 

스레드(threads)

어플리케이션이 시작하게 되면 시스템은 어플리케이션을 실행하기 위해 thread 를 만든다. 이 스레드가 main thread 라고 불린다. 이 스레드가 중요한 것이 이벤트가 발생하면 (drawing 이벤트를 포함하여) 그 이벤트를 적절한 유저 인터페이스 위젯에게 할당하는 역할을 한다. 안드로이드 UI toolkit 과 컴포넌트 사이의 상호작용도 이 스레드 내에서 일어난다. main thread 는 UI thread 라고 불리기도 한다.
 
시스템은 각 안드로이드 컴포넌트에 대해서 새로운 스레드 인스턴스(thread instance) 를 만들지 않는다. 같은 프로세스에서 동작하는 모든 컴포넌트들은 UI thread 에서 시작되며(instantiate), 각 컴포넌트들에 대한 시스템 콜(system call) 은 바로 이 스레드로 부터 처리된다(dispatch). 그러므로 시스템 콜백에 응답하는 메소드들 - onKeyDown() 이나 life cycle callback 같은 메소드들 - 은 모두 같은 프로세스의 UI 스레드에서 돌아간다.
 
만약 개발자가 개발하려는 어플리케이션의 무겁고, 강도높은 일을 할 경우, 이 모든 일을 UI 스레드에서 하게 한다면, 퍼포먼스가 상당히 떨어질 것이다. 특히, 네트워크나, DB 로부터 데이터를 읽어오는 이런 일을 할 경우, 동작 시간이 길어지면서 전체 UI 를 블락(block) 시키게 된다. thread 가 블락(block) 되게 되면, 드로잉 이벤트를 포함하여 어떤 이벤트도 처리되지 못한다. 유저 입장에서는 어플리케이션이 멈춘 것처럼 느낄 것이다. 게다가 만약 UI thread 가 5 초 이상 지속하게 되면, ANR(application not responding) 다이어로그 를 받게된다.
 
안드로이드 UI tookit 은 thread-safe 가 아니다. 그러므로, worker-thread 에서 UI 를 직접 조작하면 안된다.
UI (user interface) 에 관한 부분은 UI thread 에서 반드시 처리해 줘야 한다.
 
결론적으로 안드로이드 UI는 단일 스레드 모델이고, 다음의 간단한 두 가지 룰만 따르면 된다.
 
- UI thread 를 블락(block) 시키지 말 것! (5초 이상의 시간이 소요되는 작업 수행)
- UI thread 외의 다른 thread(worker-thread) 에서 안드로이드 UI toolkit 에 접근하지 말 것!


worker threads (UI thread를 제외한 필요에 의해 생성된 다른 thread 들) 
 위에서 봤듯이, UI thread 를 블락 시키지 않으면서 하나의 스레드 만으로 어플리케이션을 만들 경우 그 퍼포먼스는 보장할 수 없다. 그러므로 순간으로 해결할 수 있는 문제가 아니라면, 분리된 스레드 (background or worker thread) 에서 처리 해 주는 것이 좋다.

 

예를 통해 살펴보자. 아래 예는  분리된 스레드 로 부터 이미지를 다운 받아서, ImageView 에 보여주는 코드이다.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

위의 코드의 문제는 싱글 스레드 모델의 두 번째 룰을 깨고 있다. 

즉, worker thread 에서 UI toolkit 에 접근한 것이다. 이럴 경우, 빌드 자체가 안되거나 런타임 에러가 발생할 것이다. (예상하기도 힘든 행동을 보여줄 수도 있다.)

현재는 CalledFromWrongThreadException 이 발생하는 것으로 확인 했다. 

이를 고치기 위해 안드로이드에서는 다른 thread 에서 UI thread 로 접근할 수 있는 몇 가지 방법을 제시한다.

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)


위의 처음 코드를 View.post(Runnable) 메소드를 적용하여 수정하면 아래와 같다.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}


위 처럼 수정함으로써 이제 thread-safe 한 상태가 되었다.

그러나 이렇게 작성하게 되면, 코드가 길어짐에 따라 상당히 복잡해 보이고 직관적으로 이해하기 어려워질 수 있다. 보다 복잡한 작업을 위해서는 work thread 에 Handler 를 이용하는 방법이 있다. 그러나 가장 최고의 선택은, AsyncTask 클래스를 상속하여 사용하는 것이라고 한다.



AsyncTask 클래스 사용하기

AsynchTask는 유저 인터페이스에 대한 비동기적인 작업을 수행하는 것을 가능하게 한다. worker thread 에서의 작업(operation)을 블락(block) 한 후에 그 결과는 UI thread 로 보낸다. 이 때, 개발자가 thread 나 handler 를 조종하도록 요구하지 않는다.

 

이 클래스를 활용하기 위해선 AsynchTask 를 상속하여, doInBackground() 콜백 메소드를 구현한다. 이 메소드는 background thread(worker thread 라고 봐도 무방할 듯한 thread) 의 pool 에서 동작한다. 그리고 UI 를 업데이트 하기 위해서는 onPostExecute() 를 구현해야 한다. onPostExecute() 메소드는 doInBackground() 의 결과를 전달하며, UI thread 에서 동작한다. 그래서 UI (user interface) 를 안전하게 업데이트 할 수 있다. 그러고 구현하고 나서 사용방법은 해당 task 를 UI thread 에서 execute() 를 호출함으로써 실행시킬 수 있다.

 

아래는 맨 처음 코드를 AsyncTask 클래스를 상속하여 해결한 방법이다.

 

이렇게 함으로써 UI 는 thread-safe 하고 코드도 간결해졌다.
AsyncTask 의 간단한 정보는 아래와 같다.

 

  • - You can specify the type of the parameters, the progress values, and the final value of the task, using generics
  • - The method doInBackground() executes automatically on a worker thread
  • onPreExecute()onPostExecute(), and onProgressUpdate() are all invoked on the UI thread
  • - The value returned by doInBackground() is sent to onPostExecute()
  • - You can call publishProgress() at anytime in doInBackground() to execute onProgressUpdate() on the UI thread
  • - You can cancel the task at any time, from any thread

  • 이 댓글을 비밀 댓글로

    [안드로이드] 안드로이드 API GPS 상태체크

    by Blogger 하얀쿠아
    2011. 12. 4. 18:39 소프트웨어 Note/Android

    안드로이드 API GPS 상태체크


    지도를 비롯하여, 안드로이드 기기의 위치정보를 이용한 서비스 개발을 할 때, 일반적으로 기기의 현재 위도(latitude), 경도(longitude) 값이 필요하다.

    기기의 현재 위,경도 값을 알기 위해서는, 사용자가 위치정보 사용을 동의해야 한다.
    만약 동의하지 않은 상태에서 위치 획득 관련 API호출한다면,  exception을 던지게 되어있다.

    만약 아래와 같은 UX flow를 구현하려 한다면, 아래 코드를 참고하자.

    GPS 사용동의 여부를 체크 후  
        if 미동의 : 'GPS 사용 동의 설정 화면으로 이동 하겠는가?' 다이얼로그 출력. 
             if 다이얼로그로 부터 '이동한다' 입력받을 경우 : GPS 설정 화면으로 이동.

     아래는 그 기능의 예제코드이다.


    @Override
     public void onCreate(Bundle savedInstanceState) {
    	 ...
    	 String context = Context.LOCATION_SERVICE;
    	 locationManager = (LocationManager)getSystemService(context);
    	 if(!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
    		alertCheckGPS();
      }
    	 ...
     }
    
    private void alertCheckGPS() {
    	 AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	 builder.setMessage("Your GPS is disabled! Would you like to enable it?")
    			 .setCancelable(false)
    			 .setPositiveButton("Enable GPS",
    					 new DialogInterface.OnClickListener() {
    						 public void onClick(DialogInterface dialog, int id) {
    							 moveConfigGPS();
    						 }
    				 })
    			 .setNegativeButton("Do nothing",
    					 new DialogInterface.OnClickListener() {
    						 public void onClick(DialogInterface dialog, int id) {
    							 dialog.cancel();
    						 }
    				 });
    	 AlertDialog alert = builder.create();
    	 alert.show();
     }
    
    // GPS 설정화면으로 이동
    private void moveConfigGPS() {
    	Intent gpsOptionsIntent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    	startActivity(gpsOptionsIntent);
    }
    



    이 댓글을 비밀 댓글로

    자료조사(2) - 안드로이드의 오디오를 녹음하는 세 가지 방법

    by Blogger 하얀쿠아
    2011. 11. 25. 11:31 수행 프로젝트 이력/참여자주도형 정보공유 시스템 [2011.12~2012.02]

    1. Mediarecorder
    API문서: http://developer.android.com/reference/android/media/MediaRecorder.html
    사용법: http://developer.android.com/guide/topics/media/index.html


    Mediarecorder는 그 이름을 보면 알 수 있듯이, media를 record한다. audio는 마이크를 통해 녹음하여 sdcard에 그 파일을 저장한다. 녹음된 audio의 포맷은 MPEG4, RAW AMR, 3GP가 있다.


    <장점>
    1) 사용이 쉽다
    2) 오디오를 압축된 포맷으로 녹음한다.
    3) 전화 소리를 녹음할 수 있다 (수신, 송신 측 모두)
    4) 음성 인식을 녹음을 할 수 있다.


    <단점>
    1) 오디오 버퍼에 접근할 수 없다.
    2) 녹음된 오디오를 처리하기가 어렵다 – 왜냐하면 이미 압축된 포맷으로 녹음되었기 때문에.
    3) sampling rate를 바꿀 수 없다.
    4) recording을 어떻게 발생시킬 지를 거의 컨트롤할 수 없다. (very little or no control)


    2. Audiorecord
    API 문서: http://developer.android.com/reference/android/media/AudioRecord.html
    사용법: http://hashspeaks.wordpress.com/2009/06/18/audiorecord-part-4/


    Audiorecord API는 Mediarecorder의 제한을 극복하기 위한 구글의 공식적인 API다. 녹음되는 Audio는 이후의 processing을 위해서 버퍼에 저장된다. Audiorecord의 녹음 방법과 Audio처리 방법은 자바의 방법이다.


    <장점>
    1) 오디오 녹음을 MONO와 STEREO 중 선택 가능하다.
    2) sample size, sample rate, buffersize 등 다양한 오디오 레코딩 속성을 설정할 수 있다.
    3) 녹음된 audio는 버퍼를 통해 제공된다.


    <단점>
    1) Buffer 다루기가 어렵다. 만약 이게 무슨 일을 하는 지 알지 못한다면, 만든 파일을 잃어버릴지도..


    3. Audiorecord: native interface
    A LITTLE BIT HELP : http://hashspeaks.wordpress.com/2009/04/18/audiorecord-in-android-part-2/
    사용법:- ( Coming soon... how to build using NDK, sample code and how to use this API )

     native interface는 C/C++ 라이브러리를 적용한 API를 제공한다. 이 라이브러리들은 JNI를 통해 자바 액티비티로 호출 가능하다. 이 interface를 사용한 프로그램은 NDK를 통해 컴파일되고 JNI를 통해 안드로이드 애플리케이션으로 사용된다.


    이 댓글을 비밀 댓글로
      • devPCY
      • 2012.11.20 00:47
      안녕하세요^^ 일단 같은 멤버십 회원이시기에 너무 반갑습니다~
      제가 이번에 음성인식과 보이스 레코딩을 같이 구현하려고 하는데
      Mediarecorder를 이용하면 동시에 구현이 가능하다는 이 포스팅을 보구 막 개발을 해봤어요~
      근데 자꾸 죽는게, 혹시 쓰레드로 Mediarecorder를 돌리면 죽지 않을까요??
      • 반갑습니다 ^^
        제가 mediarecorder를 이용해서 음성인식과 보이스레코딩을 같이 구현해 본 경험이 없어서 이쪽 노하우가 없습니다 ^^;
        죽는 이유를 정확히 파악해야 해결할텐데 , 별도의 쓰레드로 돌리기만 해서 해결 될 문제인지 모르겠네요..
      • ㄱ나니
      • 2014.01.21 13:15
      좋은 정보 잘 정리하셔서 도움이 많이 되었습니다. ^^