[Android] Building Apps with Content Sharing (1)

Android developer site study

  • Building Apps with Content Sharing
    • Sharing Simple Data
    • Sharing Files
    • Sharing Files with NFC
https://developer.android.com/training/index.html

간단한 Data 공유하기

Android app의 중요한 기능 중 하나는 다른 app과 통신할 수 있다는 것이다.

간단한 data를 다른 app으로 전송하기

Intent 생성 시 원하는 action을 명확히 해야 한다. Android는 몇 가지 action에 대해 정의하고 있다. ACTION_SEND는 하나의 activity에서 다른 activity로 data를 보낼 때 사용된다. process 경계를 넘어갈 수도 있다.

Data를 다른 activity로 보내기 위해서는 datatype을 지정하는 것이 전부이다. 그러면 시스템은 수신이 가능한 적당한 activity를 찾아 사용자에게 보여준다(여러가지 선택이 가능하다면). 아니면 바로 해당 activity를 시작한다(하나의 선택만 가능하다면).

이와 비슷하게, 개발자는 activity에서 사용 가능한 datatype에 대해 manifest에 기술하여 다른 app들에게 알릴 수 있다.

Intetnt를 사용하여 app 사이에 data를 주고받는 것은 가장 일반적인 공유 방법이다. Intent는 쉽고 빠르게 정보를 주고 받을 수 있게 해준다.

Text 정보 전송하기

가장 간단하고 일반적인 사용법은 ACTION_SEND action을 사용하여 text를 다른 activity로 전송하는 것이다.

예를 들어, 브라우저는 현재 보고있는 페이지의 URL 정보를 다른 app과 공유할 수 있다. 이는 글이나 웹사이트를 다른 사람과 공유하고 싶을 때 편하게 사용할 수 있는 방법이다.

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent.setType("text/plain");
startActivity(sendIntent);

ACTION_SEND와 text/plane type에 맞는 filter가 존재한다면 Android 시스템은 해당 app을 구동시킬 것이다. 여러 개의 app을 발견한다면 시스템은 선택상자 (“chooser”)를 사용자에게 보여주고 선택하게 할 것이다.

하지만, Intent.createChooser()를 호출할 때 intent를 전달할 경우 항상 chooser을 보여주게 된다. 이 방법에는 몇 가지 장점이 있다.

  • 사용자가 해당 intent에 대해 default action을 지정해둔 경우에도 chooser를 보여준다.
  • 알맞은 app이 없을 때에는 Android는 시스템 메시지를 보여준다.
  • Chooser dialog의 Title을 정할 수 있다.

이를 반영한 코드는 아래와 같다.

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send");
sendIntent.setTYpe("text/plane");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));

실행 결과는 아래 그림과 같다.

선택적으로, 개발자는 몇 가지 기본 EXTRA들을 세팅할 수 있다: EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC, EXTRA_SUBJECT. 만약, 수신하는 app이 이 EXTRA들을 지원하지 않는다면 간단히 무시된다.

Gmail 등의 몇몇 e-mail app에서는 EXTRA_EMAIL, EXTRA_CC등의 EXTRA를 사용할 때 String[] 을 기대하므로 이러한 EXTRA를 intent에 추가할 때에는 putExtra(String, String[])을 사용하도록 하라.

Binary 정보 전송하기

Binary 데이터는 적당한 MIME type을 설정하여 ACTION_SEND를 사용하고 EXTRA_STREAM 내에 적절한 URI를 지정하여 공유할 수 있다.

일반적으로 image를 공유할 때 사용되지만 어떤 형태의 binary 데이터도 공유할 수 있다.

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);
shareIntent.setType("image/jpeg");
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));

아래 사항들에 대해 주의하라.

  • MIME type으로 “/“을 사용할 수 있지만 이는 generic data stream을 처리할 수 있는 activity만 이를 수신할 수 있다.
  • 수신하는 app은 Uri가 가리키는 data에 접근할 수 있는 권한을 필요로 한다. 따라서 아래와 같은 방식을 추천한다.
    • Data를 ContentProvider에 저장한 후 다른 app이 내 app의 provider에 저장할 수 있는 permission이 있는지 확인한다. 추천하는 방법은 per-URI permissions를 사용하여 접근을 준비해두는 것이다. 이는 intent를 수신하는 app에게 임시로 permission을 부여한다. 쉬운 방법으로는 FileProvider helper class를 사용하여 ContentProvider를 생성하는 것이다.
    • 시스템의 MediaStore를 사용하라. MEdiaStore는 비디오, 오디오, 이미지 MIME type에 최적화 되어있다. 하지만 Android 3.0 (API level 11)부터 non-media type들도 저장할 수 있게 되었다(자세한 것은 MediaStore.Files를 참조하라.). 공유에 적합한 content:// 스타일의 Uri 을 onScanCompleted()에 통과시킨 후 scanFile()을 사용하여 파일들을 MediaStore에 저장할 수 있다. 한 번 시스템 MediaStore에 추가한 컨텐츠는 기기의 모든 app들이 접근할 수 있다는 사실을 반드시 기억하라.

여러 개의 컨텐츠를 한 번에 보내기

여러 개의 컨텐츠를 한 번에 공유하기 위해서는 ACTION_SEND_MULTIPLE action에 URI의 목록을 넣어서 보내면 된다.

MIME type은 공유하고자 하는 컨텐츠들에 따라 넣어준다.

예를 들어, 3개의 JPEG 이미지들은 “image/jpeg”로 한다. 이미지들을 섞어서 보낸다면 “iamge/*”로 보낸다.

만약 공유하고자 하는 type들이 광범위하게 다양할 때에는 “/“를 사용한다.

시작할 때 기술했던 것처럼 해당 데이터를 수신하는 것은 수신 app의 역량에 달려있다.

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri1); // iamge URI 추가
imageUris.add(imageUri2);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "Share images to."));

다른 app으로부터 간단한 데이터를 수신하기

Manifest 업데이트하기

Intent filter를 통해 이 app 컴포넌트가 어떤 intent에 접근하길 원하는지 시스템에 알려줄 수 있다.

Intent를 수신하기 위해 <intent-filter> 를 manifest에 정의해야 한다.

만약 app이 text, 하나의 image 또는 여러 개의 image들을 수신하길 원한다면 아래와 같이 작성하면 된다.

<activity android:name=".ui.MyActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android.mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android.mimeType="image/*" />
</intent-filter>
</activity>

더 많은 정보는 Intents and Intent Filters 에서 확인할 수 있다.

다른 app이 이런 정보들을 담은 intent를 생성하고 startActivity()를 통해 공유할 때 내 app이 intent chooser의 목록에 보이게 될 것이다.

만약 사용자가 내 app을 선택한다면 그에 해당하는 activity (여기서는 .ui.MyActivity)가 시작될 것이다.

이 때부터 전달된 정보를 통해 app 내에서 가공하여 사용할 수 있다.

수신한 컨텐츠를 다루기

Intent를 통해 전달된 컨텐츠를 다루기 위해 getIntent()를 호출하여 Intent 객체를 얻어올 수 있다.

객체를 한 번 갖게 되면 컨텐츠의 내용을 살펴본 후 다음에 무엇을 할지 정하게 된다.

만약 이 activity가 launcher 등의 시스템의 다른 부분으로부터 시작이 가능한 것이라면 intent가 수신되었을 때 의도를 파악하는 것이 중요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void onCreate(Bundle savedInstanceState) {
...
// intent, action, MIME type을 얻어온다
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // 수신한 text 다루기
} else if (type.startsWith("image/")) {
handleSendImage(intent); // 수신한 이미지 처리
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
if (type.startsWith("image/")) {
handleSendMultipleImages(intent); // 수신한 여러 개의 이미지 처리
}
} else {
// 다른 intent를 처리 (ex. home screen으로부터 시작되었을 때 등)
}
...
}
void handleSendText(Intent intent) {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedTExt != null) {
// 수신한 text를 사용하여 UI 업데이트
}
}
void handleSendImage(Intent intent) {
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (imageUri != null) {
// 수신한 image를 사용하여 UI 업데이트
}
}
void handleSendMultipleImages(Intent intent) {
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (imageUris != null) {
// 수신한 image들을 사용하여 UI 업데이트
}
}

수신한 데이터를 처리할 때는 특별히 더 주의를 기해야 한다. 왜냐하면 어떤 app에서 어떤 데이터를 보낸 것인지 알 수 없기 때문이다. 예를 들어, 틀린 MIME type이 세팅되거나 엄청나게 큰 image 파일이 전송될 수도 있다.

마찬가지로, binary 데이터를 처리할 때에도 thread를 분리하여 main(UI) thread가 아닌 곳에서 처리하도록 하자.

간단한 공유 action을 추가해보기

효과적이고 사용자 친화적인 action을 처리 위해 ActionProvider와 함께 ActionBar를 사용한다면 좀 더 쉬운 구현이 가능하다.

Action bar에 한 번 메뉴 아이템이 추가되고 난 후 ActionProvider는 해당 아이템의 외견과 행동 모두를 제어한다. ShareActionProvider의 경우 개발자는 share intent를 준비하고 그것이 나머지를 수행한다.

ShareActionProvider는 API level 14 이상에서 사용 가능하다.

메뉴 선언 업데이트하기

ShareActionProvider와 함께 시작하기 위해 메뉴 리소스 파일 내의 <item>android:actionProviderClass를 정의해야 한다.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_item_share"
android:showAsAction="ifRoom"
android:title="Share"
android:action:actionProviderClass=
"android.widget.ShareActionProvider" />
...
</menu>

이 방법은 아이템의 외견 및 기능에 대한 권한을 ShareActionProvider로 이관한다. 하지만, 개발자는 공유하고 싶은 내용을 provider에 전달해줘야 한다.

Share intent 설정하기

ShareActionProvider가 기능을 수행하기 위해 개발자는 반드시 share intent를 준비해두어야 한다.

이 Share intent는 앞서 “간단한-data를-다른-app으로-전송하기”에서 본 것과 같이 ACTION_SEND action과 추가 data인 EXTRA_TEXT, EXTRA_STREAM 등을 포함하여 작성되어야 한다.

Share intent 할당을 위해 메뉴 리소스를 Activity나 Fragment에 inflating하는 동안 올바른 MenuItem을 찾아야 한다.

그 후, MenuItem.getActionProvider()를 호출하여 ShareActionProvider의 인스턴스를 찾아야 한다.

setShareIntent()를 사용하여 action item과 연관된 share intent를 업데이트 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Private ShareActionProvider mShareActionProvider;
...
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 메뉴 리소스 파일을 inflat한다.
getMenuInflater().inflate(R.menu.share_menu, menu);
// MenuItem을 ShareActionProvider와 함께 위치시킨다.
MenuItem item = menu.findItem(R.id.menu_item_share);
// ShareActionProvider를 fetch하고 저장한다.
mShareActionProvider = (ShareActionProvider) item.getActionProvider();
// 디스플레이 메뉴로 true를 리턴한다.
return true;
}
// share intent의 update를 호출한다.
private void setShareIntent(Intent shareIntent) {
if (mShareActionProvider != null) {
mShareActionProvider.setShareIntent(shareIntent);
}
}

Share intent는 메뉴를 생성할 때 한 번만 설정해두면 된다. 또는 UI 변경이 발생할 때 업데이트 할 수도 있다.

예를 들어, Gallery app에서 사진을 전체 화면으로 보면서 화면을 전환할 때, sharing intent가 변경된다.

출처

Share