사용자는 이모티콘, 스티커 및 다른 종류의 리치 콘텐츠를 사용하여 소통하고 싶어 하는 경우가 많습니다. 이전 버전의 Android에서는 소프트 키보드(입력 방식 편집기 또는 IME라고도 함)가 유니코드 이모티콘만 앱으로 전송할 수 있었습니다. 리치 콘텐츠의 경우 앱은 다른 앱에서 사용할 수 없는 앱별 API를 빌드했거나 간단한 공유 작업 또는 클립보드를 통한 이미지 전송과 같은 해결 방법을 사용했습니다.
Android 7.1 (API 수준 25)부터 Android SDK에는 Commit Content API가 포함됩니다. 이 API는 IME가 이미지 및 기타 리치 콘텐츠를 앱의 텍스트 편집기로 직접 전송할 수 있는 보편적인 방법을 제공합니다. 버전 25.0.0부터 v13 지원 라이브러리에서도 이 API를 사용할 수 있습니다. 구현을 간소화하는 도우미 메서드가 포함되어 있으므로 지원 라이브러리를 사용하는 것이 좋습니다.
이 API를 사용하면 모든 키보드에서 리치 콘텐츠를 허용하는 메시지 앱뿐만 아니라 모든 앱에 리치 콘텐츠를 보낼 수 있는 키보드에서 메시지 앱을 빌드할 수 있습니다. Google 키보드 및 Google 메시지와 같은 앱은 그림 1과 같이 Android 7.1에서 Commit Content API를 지원합니다.
이 문서에서는 IME 및 앱 모두에서 Commit Content API를 구현하는 방법을 보여줍니다.
작동 방식
키보드 이미지 삽입에는 IME 및 앱이 참여해야 합니다. 다음 시퀀스에서는 이미지 삽입 프로세스의 각 단계를 설명합니다.
사용자가
EditText
를 탭하면 편집기는EditorInfo.contentMimeTypes
에서 허용하는 MIME 콘텐츠 유형 목록을 전송합니다.IME는 지원되는 유형 목록을 읽고 편집기가 허용할 수 있는 소프트 키보드의 콘텐츠를 표시합니다.
사용자가 이미지를 선택하면 IME는
commitContent()
를 호출하고InputContentInfo
를 편집기에 전송합니다.commitContent()
호출은commitText()
호출과 비슷하지만 리치 콘텐츠를 위한 것입니다.InputContentInfo
에는 콘텐츠 제공자의 콘텐츠를 식별하는 URI가 포함됩니다.
이 프로세스는 그림 2에 묘사되어 있습니다.
앱에 이미지 지원 추가
IME의 리치 콘텐츠를 허용하려면 앱에서 허용되는 콘텐츠 유형을 IME에 알리고 콘텐츠가 수신될 때 실행되는 콜백 메서드를 지정해야 합니다.
다음 예는 PNG 이미지를 허용하는 EditText
를 만드는 방법을 보여줍니다.
Kotlin
var editText: EditText = object : EditText(this) { override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { var ic = super.onCreateInputConnection(outAttrs) EditorInfoCompat.setContentMimeTypes(outAttrs, arrayOf("image/png")) val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this) if (mimeTypes != null) { EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes) ic = InputConnectionCompat.createWrapper(this, ic, outAttrs) } return ic } }
Java
EditText editText = new EditText(this) { @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { InputConnection ic = super.onCreateInputConnection(outAttrs); EditorInfoCompat.setContentMimeTypes(outAttrs, new String[]{"image/png"}); String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this); if (mimeTypes != null) { EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes); ic = InputConnectionCompat.createWrapper(this, ic, outAttrs); } return ic; } };
자세한 설명은 다음과 같습니다.
이 예에서는 지원 라이브러리를 사용하므로
android.view.inputmethod
대신android.support.v13.view.inputmethod
참조가 있습니다.이 예에서는
EditText
를 만들고onCreateInputConnection(EditorInfo)
메서드를 재정의하여InputConnection
를 수정합니다.InputConnection
는 IME와 그 입력을 수신하는 앱 간의 통신 채널입니다.super.onCreateInputConnection()
호출은 텍스트 전송 및 수신과 같은 기본 제공 동작을 유지하고InputConnection
참조를 제공합니다.setContentMimeTypes()
는 지원되는 MIME 유형 목록을EditorInfo
에 추가합니다.setContentMimeTypes()
전에super.onCreateInputConnection()
를 호출합니다.callback
은 IME가 콘텐츠를 커밋할 때마다 실행됩니다.onCommitContent()
메서드에는 콘텐츠 URI가 포함된InputContentInfoCompat
참조가 있습니다.- 앱이 API 수준 25 이상에서 실행되고
INPUT_CONTENT_GRANT_READ_URI_PERMISSION
플래그가 IME에 의해 설정된 경우 권한을 요청 및 해제합니다. 그러지 않으면 콘텐츠 URI에 이미 액세스 권한이 있는 것입니다. 콘텐츠 URI는 IME에서 부여했거나 콘텐츠 제공자가 액세스를 제한하지 않기 때문입니다. 자세한 내용은 IME에 이미지 지원 추가를 참고하세요.
- 앱이 API 수준 25 이상에서 실행되고
createWrapper()
는InputConnection
, 수정된EditorInfo
, 콜백을 새InputConnection
에 래핑하여 반환합니다.
다음은 권장사항입니다.
리치 콘텐츠를 지원하지 않는 편집기는
setContentMimeTypes()
를 호출하지 않고EditorInfo.contentMimeTypes
를null
로 설정합니다.편집기는
InputContentInfo
에 지정된 MIME 유형이 허용되는 유형과 일치하지 않으면 콘텐츠를 무시합니다.리치 콘텐츠는 텍스트 커서의 위치에 영향을 주지 않으며 영향을 받지 않습니다. 편집기는 콘텐츠를 사용할 때 커서 위치를 무시할 수 있습니다.
편집기의
OnCommitContentListener.onCommitContent()
메서드에서 콘텐츠를 로드하기 전이라도true
를 비동기식으로 반환할 수 있습니다.커밋되기 전에 IME에서 수정할 수 있는 텍스트와 달리 리치 콘텐츠는 즉시 커밋됩니다. 사용자가 콘텐츠를 수정하거나 삭제할 수 있도록 하려면 로직을 직접 구현합니다.
앱을 테스트하려면 기기나 에뮬레이터에 리치 콘텐츠를 전송할 수 있는 키보드가 있는지 확인하세요. Android 7.1 이상에서 Google 키보드를 사용할 수 있습니다.
IME에 이미지 지원 추가
앱에 리치 콘텐츠를 전송하려는 IME는 다음 예와 같이 Commit Content API를 구현해야 합니다.
onStartInput()
또는onStartInputView()
를 재정의하고 타겟 편집기에서 지원되는 콘텐츠 유형 목록을 읽습니다. 다음 코드 스니펫은 타겟 편집기에서 GIF 이미지를 허용하는지 확인하는 방법을 보여줍니다.
Kotlin
override fun onStartInputView(editorInfo: EditorInfo, restarting: Boolean) { val mimeTypes: Array<String> = EditorInfoCompat.getContentMimeTypes(editorInfo) val gifSupported: Boolean = mimeTypes.any { ClipDescription.compareMimeTypes(it, "image/gif") } if (gifSupported) { // The target editor supports GIFs. Enable the corresponding content. } else { // The target editor doesn't support GIFs. Disable the corresponding // content. } }
Java
@Override public void onStartInputView(EditorInfo info, boolean restarting) { String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo); boolean gifSupported = false; for (String mimeType : mimeTypes) { if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) { gifSupported = true; } } if (gifSupported) { // The target editor supports GIFs. Enable the corresponding content. } else { // The target editor doesn't support GIFs. Disable the corresponding // content. } }
- 사용자가 이미지를 선택하면 앱에 콘텐츠를 커밋합니다. 작성 중인 텍스트가 있을 때는
commitContent()
를 호출하지 마세요. 편집기에서 포커스를 잃을 수 있습니다. 다음 코드 스니펫은 GIF 이미지를 커밋하는 방법을 보여줍니다.
Kotlin
// Commits a GIF image. // @param contentUri = Content URI of the GIF image to be sent. // @param imageDescription = Description of the GIF image to be sent. fun commitGifImage(contentUri: Uri, imageDescription: String) { val inputContentInfo = InputContentInfoCompat( contentUri, ClipDescription(imageDescription, arrayOf("image/gif")), null ) val inputConnection = currentInputConnection val editorInfo = currentInputEditorInfo var flags = 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION } InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null) }
Java
// Commits a GIF image. // @param contentUri = Content URI of the GIF image to be sent. // @param imageDescription = Description of the GIF image to be sent. public static void commitGifImage(Uri contentUri, String imageDescription) { InputContentInfoCompat inputContentInfo = new InputContentInfoCompat( contentUri, new ClipDescription(imageDescription, new String[]{"image/gif"}), null ); InputConnection inputConnection = getCurrentInputConnection(); EditorInfo editorInfo = getCurrentInputEditorInfo(); Int flags = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; } InputConnectionCompat.commitContent( inputConnection, editorInfo, inputContentInfo, flags, null); }
IME 작성자는 콘텐츠 URI 요청에 응답하기 위해 자체 콘텐츠 제공자를 구현해야 할 가능성이 높습니다. IME가 MediaStore
와 같은 기존 콘텐츠 제공업체의 콘텐츠를 지원하는 경우는 예외입니다. 콘텐츠 제공자 빌드에 관한 자세한 내용은 콘텐츠 제공자 및 파일 제공자 문서를 참고하세요.
자체 콘텐츠 제공업체를 빌드 중인 경우 android:exported
를 false
로 설정하여 내보내지 않는 것이 좋습니다. 대신 android:grantUriPermission
를 true
로 설정하여 제공자에서 권한 부여를 사용 설정하세요. 그러면 콘텐츠가 커밋될 때 IME에서 콘텐츠 URI 액세스 권한을 부여할 수 있습니다. 다음과 같은 두 가지 방법이 있습니다.
Android 7.1 (API 수준 25) 이상에서
commitContent()
를 호출할 때 플래그 매개변수를INPUT_CONTENT_GRANT_READ_URI_PERMISSION
로 설정합니다. 그러면 앱이 수신하는InputContentInfo
객체가requestPermission()
및releasePermission()
를 호출하여 임시 읽기 권한을 요청하고 해제할 수 있습니다.Android 7.0 (API 수준 24) 이하에서는
INPUT_CONTENT_GRANT_READ_URI_PERMISSION
가 무시되므로 수동으로 콘텐츠에 권한을 부여합니다. 이렇게 하는 한 가지 방법은grantUriPermission()
를 사용하는 것이지만 자체 요구사항을 충족하는 자체 메커니즘을 구현해도 됩니다.
IME를 테스트하려면 기기나 에뮬레이터에 리치 콘텐츠를 수신할 수 있는 앱이 있는지 확인합니다. Android 7.1 이상에서 Google Messenger 앱을 사용할 수 있습니다.