'개발'에 해당되는 글 188건
- 2010.12.03 Intent에 의한 사진뷰어, 갤러리, 카메라 제어 1
- 2010.10.22 manifest-element 15
- 2010.10.13 C2DM
- 2010.10.13 [번역] 안드로이드 Android Cloud to Device Messaging(C2DM)
- 2010.08.17 OMA DRM에 대한 개요
- 2010.07.06 웹개발자를 위한 안드로이드 강좌-설치
- 2010.07.06 안드로이드 http connection UTF-->euc-kr 7
- 2010.06.25 안드로이드 하이브리드 앱 - 1. WebView로 로컬 파일(HTML) 로드하기 2
- 2010.06.15 Android Fundamental 중 Activity와 Task
- 2010.04.22 back key overriding
Intent에 의한 사진뷰어, 갤러리, 카메라 제어
Intent i = new Intent(Intent.ACTION_PICK); i.setType(android.provider.MediaStore.Images.Media.CONTENT_TYPE); i.setData(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); // images on the SD card.
// 결과를 리턴하는 Activity 호출 startActivityForResult(i, REQ_CODE_PICK_PICTURE);
img.setImageURI(data.getData());
// 사진 선택한 사진URI로 연결하기
// ex>uri = content://media/external/images/media/4 --> 4 = id
Uri uri = ContentUris.withAppendedId( Images.Media.EXTERNAL_CONTENT_URI, id); // id값은 사진고유 ID
Intent intend = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intend);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
manifest-element
<manifest>
- syntax:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="string"
android:sharedUserId="string"
android:sharedUserLabel="string resource"
android:versionCode="integer"
android:versionName="string"
android:installLocation=["auto" | "internalOnly" | "preferExternal"] >
. . .
</manifest>- contained in:
- none
- must contain:
<application>
- can contain:
<instrumentation>
<permission>
<permission-group>
<permission-tree>
<uses-configuration>
<uses-permission>
- description:
- The root element of the AndroidManifest.xml file. It must contain an
<application>
element and specifyxlmns:android
andpackage
attributes. - attributes:
-
xmlns:android
- Defines the Android namespace. This attribute should always be set to "
http://schemas.android.com/apk/res/android
". package
- A full Java package name for the application. The name should be unique. The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers, and underscores ('_'). However, individual package name parts may only start with letters. For example, applications published by Google could have names in the form
com.google.app.application_name
.The package name serves as a unique identifier for the application. It's also the default name for the application process (see the
<application>
element'sprocess
process attribute) and the default task affinity of an activity (see the<activity>
element'staskAffinity
attribute). android:sharedUserId
- The name of a Linux user ID that will be shared with other applications. By default, Android assigns each application its own unique user ID. However, if this attribute is set to the same value for two or more applications, they will all share the same ID — provided that they are also signed by the same certificate. Application with the same user ID can access each other's data and, if desired, run in the same process.
android:sharedUserLabel
- A user-readable label for the shared user ID. The label must be set as a reference to a string resource; it cannot be a raw string.
This attribute was introduced in API Level 3. It is meaningful only if the
sharedUserId
attribute is also set. android:versionCode
- An internal version number. This number is used only to determine whether one version is more recent than another, with higher numbers indicating more recent versions. This is not the version number shown to users; that number is set by the
versionName
attribute.The value must be set as an integer, such as "100". You can define it however you want, as long as each successive version has a higher number. For example, it could be a build number. Or you could translate a version number in "x.y" format to an integer by encoding the "x" and "y" separately in the lower and upper 16 bits. Or you could simply increase the number by one each time a new version is released.
android:versionName
- The version number shown to users. This attribute can be set as a raw string or as a reference to a string resource. The string has no other purpose than to be displayed to users. The
versionCode
attribute holds the significant version number used internally. android:installLocation
- The default install location for the application.
The following keyword strings are accepted:
Value Description " internalOnly
"The application must be installed on the internal device storage only. If this is set, the application will never be installed on the external storage. If the internal storage is full, then the system will not install the application. This is also the default behavior if you do not define android:installLocation
." auto
"The application may be installed on the external storage, but the system will install the application on the internal storage by default. If the internal storage is full, then the system will install it on the external storage. Once installed, the user can move the application to either internal or external storage through the system settings. " preferExternal
"The application prefers to be installed on the external storage (SD card). There is no guarantee that the system will honor this request. The application might be installed on internal storage if the external media is unavailable or full, or if the application uses the forward-locking mechanism (not supported on external storage). Once installed, the user can move the application to either internal or external storage through the system settings. Note: By default, your application will be installed on the internal storage and cannot be installed on the external storage unless you define this attribute to be either "
auto
" or "preferExternal
".When an application is installed on the external storage:
- The
.apk
file is saved to the external storage, but any application data (such as databases) is still saved on the internal device memory. - The container in which the
.apk
file is saved is encrypted with a key that allows the application to operate only on the device that installed it. (A user cannot transfer the SD card to another device and use applications installed on the card.) Though, multiple SD cards can be used with the same device. - At the user's request, the application can be moved to the internal storage.
The user may also request to move an application from the internal storage to the external storage. However, the system will not allow the user to move the application to external storage if this attribute is set to
internalOnly
, which is the default setting.Introduced in: API Level 8.
- The
- introduced in:
- API Level 1 for all attributes, unless noted otherwise in the attribute description.
- see also:
- App Install Location
<application>
<uses-sdk>
C2DM
C2DM (Cloud to Device Messaging) 은 인터넷에서 안드로이드 폰에 메시지를 보낼수 있는 기술이다.
[번역] 안드로이드 Android Cloud to Device Messaging(C2DM)
- 안드로이드 2.2 버전이 필요합니다.
- C2DM 은 '구글 서비스'를 사용합니다. 이 서비스는 안드로이드 마켓을 사용하는 모든 디바이스에 존재합니다.
- C2DM 은 '구글 서비스' 를 위해 이미 존재하는 커넥션을 사용합니다.
- C2DM 은 안드로이드 폰 상에서 사용자가 구글 계정으로 로그인 해야 사용이 가능합니다.
- C2DM은 서드파티 서버가 간단한 메세지를 자신들의 어플리케이션으로 전달하는 것을 허용합니다.
- C2DM 서비스는 대량의 컨텐츠를 푸쉬하도록 설계되지 않았습니다. 대신 특정 어플리케이션에게 새로운 데이타가 있음을 '쿡' 하고 알려 주고, 어플리케이션이 해당 서버에 접속해서 데이타를 다운로드 받을 수 있도록 하는데 사용되어야 합니다.
- 어플리케이션은 메세지를 받기 위해 작동중일 필요가 없습니다. 시스템은 전달해야할 데이타가 도착하는 경우에, 브로드캐스트 인텐트를 이용해 해당 어플리케이션을 깨울 것 입니다. 따라서, 어플리케이션은 적절하게 브로드캐스트 리시버와 Permission 을 설정해야 합니다.
- 데이타 메세지를 전달 받기 위해 사용자 인터페이스가 필요하지는 않습니다. 물론 어플리케이션이 원한다면 알림창에 노티피케이션을 날릴 수도 있을 것 입니다.
- C2DM 을 사용하기 위해 디바이스 상의 어플리케이션은 우선 구글에 등록해 Registration ID 를 발급 받아야 합니다. 해당 ID 를 자신의 서버에 전달해야 합니다.
- 만일 자신의 서버가 푸시하고 싶은 메세지가 있을 경우, 메세지를 HTTP 를 통해 구글의 C2DM 서버에 전달합니다.
- C2DM 서버는 메세지를 디바이스에 라우팅 하고, 디바이스는 브로드캐스트 인텐트를 어플리케이션에 전달 할 것 입니다.
- 타켓 어플리케이션은 브로드 캐스트 인텐트를 통해 깨어나고 메세지를 처리합니다.
- 어플리케이션은 사용자가 더이상 푸시 서비스를 받고싶지 않을 경우 등록을 취소할 수 있습니다.
// Use the Intent API to get a registration ID
// Registration ID is compartmentalized per app/device
Intent regIntent = new Intent(
"com.google.android.c2dm.intent.REGISTER");
// Identify your app
regIntent.putExtra("app",
PendingIntent.getBroadcast(this /* your activity */,
0, new Intent(), 0);
// Identify role account server will use to send
regIntent.putExtra("sender", emailOfSender);
// Start the registration process
startService(regIntent);
// Registration ID received via an Intent
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (“com.google.android.c2dm.intent.REGISTRATION”.equals(action)) {
handleRegistration(context, intent);
}
}
public void handleRegistration(Context context, Intent intent) {
String id = intent.getExtra(“registration_id”);
if ((intent.getExtra(“error”) != null) {
// Registration failed. Try again later, with backoff.
} else if (id != null) {
// Send the registration ID to the app’s server.
// Be sure to do this in a separate thread.
}
}
- Authorization: GoogleLogin auth=<auth token>
- Registration ID 와 키/벨류 쌍으로 이루어진 데이타, Google C2DM 서버에서 동일한 키값을 갖고 있는 오래된 메세지를 가로채기 위해 사용되는 'Collapse Key' 등몇 가지 옵셔널한 파라매터들을 포함하도록 인코딩된 URL.
OMA DRM에 대한 개요
DRM은 Digital Right Management의 약자로 한마디로 디지털 저작권 보호를 위한 컨텐츠 암호화 또는 이를 위한 방법을 의미한다.
OMA DRM이라고 하면 OMA는 Open Mobile Alliance의 약자로 모바일 기기에서의 컨텐츠 보호를 위하여 구성한 컨소시움 정도라고 생각하면 된다. 따라서 OMA에서는 모바일 기기에서의 저작권 보호를 위한 다양한 기술 스펙을 제공한다고 하면 대충 정리가 될 것 같다. OMA DRM은 OMA에서 제안하는 DRM 방식이라고 하면 될 것 같은데 특히, Mobile Phone을 위한 DRM 스펙이라고 보면 된다. 최근에 사용하는 Mobile Phone의 유료 컨텐츠에는 대부분 OMA DRM을 이용하여 암호화를 한다고 생각하면 된다.
우리가 많이 들어본 SKT의 멜론서비스, KTF의 도시락 서비스 등 유료화된 음악 서비스는 OMA DRM이 걸려있다.
보통 DRM을 위해서는 암호화시키는 방법이 제일이다. 사용자는 암호화된 데이터를 받고 이를 풀기 위해서는 특정 키를 이용해야만 한다.
OMA DRM에서 서비스로 제공되는 컨텐츠는 다음과 같이 크게 3가지 방식으로 구분된다.
1. Forward Lock
Forward Lock은 컨텐츠 자체를 보호한다기 보다는 컨텐츠를 서로 공유할 수 없게 하는 방법이다.
초기 핸드폰에서는 무선 서비스로 컨텐츠를 다운 받았는데 이러한 컨텐츠는 특별한 암호화가 이루어지지 않았지만 PC 또는 다른 핸드폰으로 이동시킬 수 없었다. 다른 장치로 이동할 수 없기 때문에 사용이 허가된 사람만이 사용할 수 있어서 저작권을 보호하게 된다.
2. Combined Delivery
Combined Delivery에서는 우리가 어떤 컨텐츠를 받는다면 이에는 컨텐츠(Contents)와 권리 객체(Rights Object)가 하나로 묶여서 암호화 되어 있다. 권리란 것은 보통 허가(Permission)나 제한(Constraint)에 대한 정보(즉 이 컨텐츠는 몇번 재생 가능하다, 몇일까지 재생 가능하다는 등의 정보)를 의미하는데, 권리 객체가 같이 묶여 있기 때문에 키만 있으면 권리의 내용에 따라 바로 사용이 가능하다. OMA에서는 Forward Lock과 마찬가지로 Combined Delivery에 의한 데이터는 다른 장치로 이동하지 못하도록 해야 한다고 되어 있다.
3. Sepearte Delivery
Seperate Delivery는 말 그대로 컨텐츠와 권리 객체가 분리되어 있음을 나타낸다. 이런 경우에 컨텐츠와 권리 객체는 각각 암호화 되어 있다. 암호화된 컨텐츠는 다른 장비로 이동시켜도 관계없다. 권리 객체가 없으면 실제 이동된 컨텐츠는 사용할 수 없기 때문이다. 권리 객체는 무선 서비스 등을 통해서 별도로 받을 수 있다. 권리 객체를 받게 되면 이동된 컨텐츠를 바로 사용하는 것이 가능하다.
왜 Combined Delivery와 Seperate Delivery가 구분되어야 하냐면 보통 컨텐츠의 권리가 만료되었을 때 Combined Delivery는 다시 모든 정보를 받아야만 사용이 가능하다. 그러나 Seperate Delivery는 권리 객체만 따로 받으면 사용 가능하다. 이는 사용자 입장에서 보면, 같은 컨텐츠를 사용하기 위해서 많은 데이터를 2-3번 받을 필요없다는 것이다. 데이터는 1번만 받고 권리만 만료되었을 때 받으면 된다.
마찬가지로 다른 사용자에게 이동식 메모리(SD 카드, T-Flash)로 빠르게 복사한 경우에도 다른 사용자는 권리를 받아서 재사용이 가능하다. (물론 다른 사용자가 권리를 받을 경우에는 사용에 대한 돈은 지불해야 한다. 돈을 내야 권리를 주기 때문이다.)
웹개발자를 위한 안드로이드 강좌-설치
안드로이드 SDK 설치 및 실행
eclipse 3.5.1 기준으로 설명함
JDK .5.0 기준
1. Java SDK 설치
http://java.sun.com/javase/downloads/index.jsp
2. 이클립스 설치
http://www.eclipse.org/downloads/
3. Android SDK 설치
http://developer.android.com/sdk
참고 디텍토리 위치에 한글 포함되면 안됨(중요)
4. 안드로이드 Eclipse 플러그인 설치(ADT)
Help/Install New Software
Add 버튼 클릭
Name엔 적당히 Android PlugIn이라고 채우시고, Location에 https://dl-ssl.google.com/android/eclipse/ 넣음
OK를 누르고 리스트에 표시된 Developer Tools 라고 되어있는 체크박스를 체크한후 Next, Next, 약관 동의, Finish
위 URL이 안된다면 http://dl-ssl.google.com/android/eclipse/로도 시도해보세요. (https -> http)
저는 https를 실패하여 http로 접속하였음
5. 이클립스 안드로이드 SDK 설정
왼쪽 탭에서 Android 선택
Browse를 한후 SDK를 설치한 디렉토리 선택 (디렉토리 패스에 한글이 들어가 있으면 안됩니다.)
Apply후 OK
6. 안드로이드 버전별 다운로드
안드로이드 2.0 SDK 부터 새로 생긴 방식입니다. 각 버전별 에뮬레이터 및 SDK를 별도로 다운로드 받게 되어있습니다.
Window/Android SDK and AVD Manager 실행
Available 패키지에서 설치를 원하는 API 버전 선택
(현재 https로 시작되는 것은 에러가 나는 경우가 종종 있습니다. 이경우 http://로 시작하는 주소를 Add Site로 추가합니다.
Install Selected
설치가 모두 완료되었습니다.
안드로이드 http connection UTF-->euc-kr
package com.qnsolv.vanmsm.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Hashtable; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.HTTP; import com.qnsolv.vanmsm.common.SI; /* * HttpUtil client = new HttpUtil(LOGIN_URL); client.AddParam("accountType", "GOOGLE"); client.AddParam("source", "tboda-widgalytics-0.1"); client.AddParam("Email", _username); client.AddParam("Passwd", _password); client.AddParam("service", "analytics"); client.AddHeader("GData-Version", "2"); try { client.Execute(RequestMethod.POST); } catch (Exception e) { e.printStackTrace(); } String response = client.getResponse(); */ public class HttpUtil { private String defaultUrl="";//자신의 서버url private ArrayListparams; private ArrayList headers; private String url; private int responseCode; private String message; private String response; public String METHOD_GET = "GET"; public String METHOD_POST = "POST"; public int networkTime=20000; public Hashtable getResponse() { return VanUtil.packetAnalyis(response); } public String getErrorMessage() { return message; } public int getResponseCode() { return responseCode; } public HttpUtil(String url) { this.url = defaultUrl+url; params = new ArrayList (); headers = new ArrayList (); } public void AddParam(String name, String value) { params.add(new BasicNameValuePair(name, value)); } public void AddHeader(String name, String value) { headers.add(new BasicNameValuePair(name, value)); } public void execute(String method) throws Exception { if (method.equals(METHOD_GET)) { String combinedParams = ""; if (!params.isEmpty()) { combinedParams += "?"; for (NameValuePair p : params) { String urlEncoderStr=""; try { urlEncoderStr = URLEncoder.encode(p.getValue(),"euc-kr"); } catch (Exception e) { // TODO: handle exception } String paramString = p.getName() + "=" + urlEncoderStr; if (combinedParams.length() > 1) { combinedParams += "&" + paramString; } else { combinedParams += paramString; } } } VanUtil.println("DEBUG",this.getClass().getName() + " :="+url + combinedParams); HttpGet requestGet = new HttpGet(url + combinedParams); // add headers requestGet.addHeader("Accept", "text/plain, text/html, text/*"); requestGet.addHeader("Referer", "vw.qnsolv.com"); requestGet.addHeader("Host", "vw.qnsolv.com:8080"); requestGet.addHeader("Connection", "Keep-Alive"); requestGet.addHeader("User-Agent", ""); requestGet.addHeader("Content-Type", "text/html;charset=UTF8"); executeRequest(requestGet, url); } else if (method.equals(METHOD_POST)) { HttpPost request = new HttpPost(url); request.addHeader("Accept", "text/plain, text/html, text"); if (!params.isEmpty()) { request.setEntity(new UrlEncodedFormEntity(params, HTTP.ISO_8859_1)); } executeRequest(request, url); } } private void executeRequest(HttpUriRequest request, String url) { HttpParams httpParameters = new BasicHttpParams(); int timeoutConnection = networkTime; HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); int timeoutSocket = networkTime; HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); HttpClient client = new DefaultHttpClient(httpParameters); HttpResponse httpResponse; try { httpResponse = client.execute(request); responseCode = httpResponse.getStatusLine().getStatusCode(); message = httpResponse.getStatusLine().getReasonPhrase(); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); response = convertStreamToString(instream); Hashtable ht = VanUtil.packetAnalyis(response); // Closing the input stream will trigger connection release instream.close(); } } catch (ClientProtocolException e) { client.getConnectionManager().shutdown(); e.printStackTrace(); response=SI.gI().getVanDefaultCode()+"00000100W99네트워크 연결 지연 \n( 네트워트 연결시간 "+(networkTime/1000)+" 초) "; } catch (IOException e) { client.getConnectionManager().shutdown(); e.printStackTrace(); response=SI.gI().getVanDefaultCode()+"00000100W99네트워크 연결 지연 \n( 네트워트 연결시간 "+(networkTime/1000)+" 초) "; } } private static String convertStreamToString(InputStream is) { StringBuilder sb = new StringBuilder(); try{ BufferedReader reader= new BufferedReader(new InputStreamReader(is,"EUC-KR")); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } }catch(Exception e){ } return sb.toString(); } }
안드로이드 하이브리드 앱 - 1. WebView로 로컬 파일(HTML) 로드하기
로컬 HTML(JavaScript)과 App 영역이 통신(함수호출)을 함으로써 간단한 하이브리드 앱을 만들어볼 수 있다.
1. HTML에서 App 함수 호출
1) 멤버로 android.os.Handler 를 생성한다. 호출 시 thread 처리를 위해서 이용된다.
private final Handler handler = new Handler();
2) App과 Javascript간 Bridge 클래스를 생성하고, 호출될 함수를 implement 한다.
(이 때 파리메터는 반드시 final로 선언)
Javascript에서 호출시 별도의 Thread로 구동될 수 있도록 아래와 같이 구현한다.
public void setMessage(final String arg) { // must be final
handler.post(new Runnable() {
public void run() {
Log.d("HybridApp", "setMessage("+arg+")");
mTextView.setText(arg);
}
});
}
}
mWebView.getSettings().setJavaScriptEnabled(true);
// Bridge 인스턴스 등록
mWebView.addJavascriptInterface(new AndroidBridge(), "HybridApp");
4) HTML 내에서 JavaScript에서 선언된 함수를 다음과 같이 호출 한다.
window.<interfaceName>.<functionName>
2. App에서 HTML의 Javascript 함수 호출
이부분은 간단하다....HTML에거 링크걸 때를 생각하면 되는데....
그냥 버튼을 눌렀을 때 다음과 같이 호출하면 된다.
mWebView.loadUrl("javascript:<함수명>('<arg>')");
실제 구현은 다음과 같이 된다.
public void onClick(View view) {
mWebView.loadUrl("javascript:setMessage('"+mEditText.getText()+"')");
}
});
Android Fundamental 중 Activity와 Task
출처 : [KANDROID]
하나의 activity는 다른 activity를 시작할 수 있고 다른 application의 activity도 시작할 수 있다. 예를 들어 임의위치의 street map을 display한다고 가정하자, 이런 작업을 하는 activity는 이미 제공된다. 따라서 당신의 activity에서 이 activity를 시작하기 위해 해야 할 일은 필요한 정보를 포함하는 intent object를 startActivity()에 파라메터로 전달하여 호출하는것 뿐이다. 이렇게 하면 street map viewer가 표시될것이다. 이 때 BACK버튼을 누르면 viewer를 시작한 당신의 activity가 다시 보여질 것이다.
이는 사용자로 하여금 street map viewer가 당신의 application의 일부라고 느끼게 한다. 하지만 실제로는 그 activity는 다른 app의 다른 process상의 activity이다.
안드로이드는 이처럼 사용자가 사용한 activity들을 task로 하여 그 정보를 유지한다. 관련된 activity는 group으로 stack에 저장된다.
root activity는 task상의 첫번째 activity이고 top activity는 현재 화면에 보여지는 activity이다. activity가 다른 activity를 시작하면 그 새로운 activity가 stack에 push되고 그 activity가 top activity가 된다. 그리고 이전 activity는 stack에 남아 있는다. 이 상태에서 사용자가 BACK버튼을 누르면 이전 activity가 stack에서 POP되어 화면에 보여지게 되어 resume된다.
stack은 activity의 object(instance)를 가지고 있다. 따라서 같은 activity의 여러개의 instance가 가능하다. 같은 activity를 여러개 시작할수 있다는 의미이다.
stack내의 activity는 stack이므로 재정렬되지 않는다. 순서는 그대로 유지되게 된다. 단지 PUSH, POP만 된다.
Task는 activity들의 stack이다. 따라서 task내의 activity에 어떤 값을 설정하는 방법은 없다. root activity만이 affinity(친밀도) set을 이용하여 read, set이 가능하다.
Task의 모든 activity들은 하나의 집합으로 background또는 foreground로 이동한다. 현재 Task가 4개의 activity를 가진다고 가정해보자. HOME 키를 누르면 application launcher로 이동한다. 이어서 새로운 application을 실행한다. 그러면 현재 task는 background로 가고 새로운 task의 root activity가 표시된다. 이어 사용자가 다시 HOME으로 갔다가 이전 application을 다시 선택한다면 그 task가 다시 앞으로 나온다. 이 때 BACK키를 누르면 root activity가 표시되지 않고 task상의 이전 activity가 표시된다.
A1 -> A2 -> A3 -> A4 -> HOME -> B 1-> B2 -> HOME -> A4 -> BACK -> A3
task와 activity간의 결합과 동작에 대한 제어는 intent object의 flag 파라메터와 minifest의 <activity> element의 몇가지 속성으로 제어가 가능하다.
flag -> FLAG_ACTIVITY_NEW_TASKFLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, FLAG_ACTIVITY_SINGLE_TOP
<activity>'s attributes -> taskAffinity, launchMode, allowTaskReparenting, clearTaskOnLaunch, allowRetainTaskState, finishOnTaskLaunch
Affinityes and new Tasks
기본적으로 하나의 application의 activity들은 각기 하나의 affinity를 갖는다. 그러나 각각의 affinity들은 <activity> element의 taskAffinity속성으로 affinity set을 이룰수 있다. 서로 다른 application의 activity들이 동일한 affinity를 공유할 수 있으며 한 application의 activity들이 서로 다른 affinity를 가질수 있다. affinity는 intent object에 FLAG_ACTIVITY_NEW_TASK로 activity를 적재할 때와 activity가 allowTaskReparenting속성을 true로 set하였을 때 시작된다.
FLAG_ACTIVITY_NEW_TASK 적용시
앞서 기술한대로 기본적으로 activity는 startActivity()로 task안에 적재된다. caller와 동일한 stack에 push된다. 그러나 startActivity()가 FLAG_ACTIVITY_NEW_TASK 로 flag를 set하여 호출하면 시스템은 새로운 activity를 담기위한 task를 찾는다. 보통 새로운 task가 생성되지만 동일한 affinity를 갖는 task가 검색되면 그 태스크에 가서 달라붙는다.
allowTaskReparenting 적용시
특정 activity가 allowTaskReparenting속성이 "true"이면, 시작된 task에서 동일한 affinity를 갖는 다른 task가 foreground로 올라올때 그 task로 activity가 이동될 수 있다. 예를 들면, 특정도시의 날씨를 보여주는 activity를 가지고 있는 travel application이 있다고 하자. travel application에 동일한 affinity를 갖는 다른 activity가 있고 reparenting이 가능하다. 이 상태에서 당신의 application의 activity가 travel application의 날씨 activity를 시작하면 날씨 activity는 당신의 task에 적재되게 된다. 그러나 이 때 travel application이 적재되게 되면 날씨 activity는 새로 시작된 travel application의 task에 재위치지정이 되고 화면에 표시되어진다.
travel application : Weather activity, ... -> allowTaskReparenting이 true이고 모두 동일 affinity를 갖는다.
your application : A, B, C, D activity
launch travel application -> (1)start Weather activity -> HOME -> launch your application -> start A activity -> (2)start Weather activity -> HOME -> (3)travel application -> display Weather activity
(1) 시점에서 weather activity는 task1(travel app의 task)에 적재된다. (2)시점에서 weather activity는 task2(your app의 task)에 적재된다. (3)의 시점에서 travel app가 다시 시작될때 task2에 있던 weather activity가 task1으로 재지정되게 된다.
하나의 패키지(*.apk)에 여러 application이 정의되어 있다면 app단위로 각기 다른 affinity를 부여하는것이 좋다.
Launch Mode
<activity> element에 lounchMode 속성을 조정하여 activity를 컨트롤할 수 있다.
standard, singleTop, singleTask, singleInstance
위 4가지 모드는 4가지 관점에서 다르게 동작한다.
* 임의 Intent에 대해 어떤 task가 그 activity를 받을 것인가?
standard, singleTop모드는 intent가 발생된 task에 push된다. flag를 FLAG_ACTIVITY_NEW_TASK로 설정해도 호출한 동일한 task내에 push된다. 위에 기술한 affinity & new task에 따라 다른 task가 선택될수 있다.
singleTask및 singleInstance는 task를 정의하여 root activity로 적재되고 다른 task에 적재되지 않는다.
* 다중 instance activity가 가능한가?
standard, singleTop모드는 여러 task에 소속될수도 있고 한 task에 동일한 activity가 여러 instance가 적재될수도 있다.
singleTask, singleInstance는 task내에서 오로지 한개의 instance만 적재된다. root activity만 가능하기 때문에 device내에서 한번에 하나의 Instance만 존재할 수 있다.
* Instance가 task내에서 다른 activity를 가질수 있는가?
singleInstance는 task내에서 오직 하나의 instance만 가능하며, 만일 다른 activity를 시작하면 launchMode에 관계없이 새로운 task가 생성되어 적재된다.
standard, singleTask, singleTop은 모두 multi instance가 가능하다. singleTask는 root activity로 생성되며 다른 activity를 task내에 적재가 가능하다. singleTop과 standard모드는 stack내에서 자유롭게 다른 activity를 생성가능하다.
* 특정 class의 새로운 instance가 새로운 intent를 다룰것인가?
standard모드는 새로운 instance가 새로운 intent의 응답으로 생성된다. 각 instance는 오직 하나의 intent를 다룬다.
singleTop : target-task의 stack에 top activity로 있다면 그 class의 instance가 intent를 재사용하여 처리한다. top activity가 아니면 재사용하지 않고 새로운 instance가 생성되어 intent를 처리하고 stack에 push된다.
예) A - B - C - D에서 D를 시작하려고 할 때 D가 singleTop이면 A - B - C - D 로된다.
A - B - C - D에서 D를 시작하려고 할 때 D가 standard이면 A - B - C - D - D 로된다.
B가 singleTop이나 standare이면 A - B - C - D - B 가 가능하다.
Clearing the stack
기본적으로 사용자가 task를 오랫동안 사용하지 않으면 system은 task의 root activity만을 제외하고 모든 activity들을 clear한다. <activity>element의 몇가지 속성은 이를 조정할 수 있게 해준다.
alwaysRetainState 속성
task의 root activity에 이 속성을 set하면 이 task는 오랜시가이 지나도 생존하게 된다.
clearTaskOnLaunch 속성
task의 root activity에 이 속성을 set하면 task를 나가고 돌아올때 clear된다.
finishOnTaskLaunch
clearTaskOnLaunch와 유사하나 이 속성은 하나의 activity에만 유효하다. root activity를 포함하여 현재 세션인 경우에만 살아있고 task를 떠나면 clear된다.
stack에서 activity를 제거하는 다른 방법이 있다.
intent object의 flag를 FLAG_ACTIVITY_CLEAR_TOP로 하고 이 intent를 처리할 activity가 target task에 이미 instance를 가지고 있다면 상위 activity들이 모두 clear되고 그 activity가 top activity가 된다. launchMode가 "standard"라면 stack에서 마찬가지로 삭제되고 새로운 activity가 그 intent를 처리할 것이다.
FLAG_ACTIVITY_NEW_TASK와 FLAG_ACTIVITY_CLEAR_TOP이 함께 사용되면 존재하는 activity가 새 task에 생성되어 intent를 처리한다.
Starting task
activity는 intent filter중에 action filter로 android.intent.action.MAIN를 그리고 category filter로 android.intent.category.LAUNCHER로 entry point가 설정된다. 이런 설정은 icon과 label정보를 가지로 화면에 표시하고 task에 적재하고 적재후 언제든지 다시 돌아올수 있도록 해준다.
사용자는 언제든 task를 떠날수 있고 다시 돌아올수 있다. singleTask와 singleInstance로 설정된 activity는 반드시 MAIN과 LAUNCHER를 filter로 적용해야 한다. 그러지 않으면 activity를 수행후 다른 화면으로 갔다가 다시 돌아올 수 있는 방법이 없게 된다.
FLAG_ACTIVITY_NEW_TASK 는 activity하나가 새로운 task에 시작되고 HOME key를 눌렀을 경우 다시 복귀하기 위해 다른 방법이 있다.
외부 task에서 notification manager같은 entity에서 항상 activity들을 시작할 수 있다. 이런 방식으로 외부에서 activity를 invoke할 수 있다면 사용자가 다른 방법으로 그 task를 시작할 수 있음을 유의해야 한다. 이런 경우 그 activity로 복귀하기를 원하지 않는다면 finishOnTaskLaunch를 사용하면 된다.
back key overriding
OnKeyDown() 함수를 overriding하면 됨
참고해야할 사항은 back key를 눌러서 platform에서 이전 activity로 돌아가는 처리를 하는 이벤트가
ACTION_DOWN이벤트라는 거다. (key down + back key가 들어오면 자동종료되고 이후에 key up등은 안들어옮)
그래서 굳이 UP에서도 처리할 필요없고 아래와 같이 처리를 하면 된다.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
// Do your job~~
}
return true;
}
위의 경우에는 내가 모든 key를 무조건 다 먹어버리는데 back키만 오버라이드하고 나머지는 platform이 알아서
후처리를 하게 하고 싶다면 아래와 같이 변경
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
// Do your job~~
} else {
super.onKeyDown(keyCode, event);
}