이 페이지에서는 앱에서 환경을 설정하고 Slice를 빌드하는 방법을 보여줍니다.
참고: Android 스튜디오 3.2 이상에는 Slice 개발에 도움이 되는 추가 도구와 기능이 포함되어 있습니다.
- AndroidX 리팩터링 도구: AndroidX 라이브러리를 사용하는 프로젝트에서 작업하는 경우 필요합니다.
- Slice 린트 검사: Slice를 빌드할 때 규정에 어긋나는 일반적인 오류를 포착합니다.
SliceProvider
템플릿:SliceProvider
를 빌드할 때 상용구를 처리합니다.
슬라이스 뷰어 다운로드 및 설치
SliceView
API를 구현하지 않고 Slice를 테스트하는 데 사용할 수 있는 최신 샘플 Slice 뷰어 APK 버전을 다운로드하세요.
개발자 환경에서 ADB가 제대로 설정되지 않았다면 자세한 내용은 ADB 가이드를 참조하세요.
다운로드된 slice-viewer.apk
와 동일한 디렉터리에서 다음 명령어를 실행하여 기기에 Slice 뷰어를 설치하세요.
adb install -r -t slice-viewer.apk
Slice 뷰어 실행
Slice 뷰어는 Android 스튜디오 프로젝트 또는 명령줄에서 실행할 수 있습니다.
Android 스튜디오 프로젝트에서 Slice 뷰어 실행
- 프로젝트에서 Run > Edit Configurations...를 선택합니다.
- 왼쪽 상단에서 녹색 더하기 기호를 클릭합니다.
Android 앱 선택
Name 입력란에 Slice를 입력합니다.
Module 드롭다운에서 앱 모듈을 선택합니다.
Launch Options 아래 Launch 드롭다운에서 URL을 선택합니다.
URL 입력란에
slice-<your slice URI>
를 입력합니다.예:
slice-content://com.example.your.sliceuri
OK를 클릭합니다.
ADB(명령줄)를 통해 Slice 뷰어 도구 실행
Android 스튜디오에서 앱을 실행합니다.
adb install -t -r <yourapp>.apk
다음 명령어를 실행하여 Slice를 확인합니다.
adb shell am start -a android.intent.action.VIEW -d slice-<your slice URI>
단일 WiFi Slice를 보여주는 Slice 뷰어
한 곳에서 모든 Slice 보기
단일 Slice를 실행하는 것 외에 Slice의 영구 목록을 볼 수 있습니다.
- 검색창을 사용하여 URI(예:
content://com.example.android.app/hello
)를 통해 Slice를 수동으로 검색합니다. 검색할 때마다 Slice가 목록에 추가됩니다. - Slice URI를 사용하여 Slice 뷰어 도구를 시작할 때마다 Slice가 목록에 추가됩니다.
- Slice를 스와이프하여 목록에서 삭제할 수 있습니다.
- Slice의 URI를 탭하면 이 Slice만 포함된 페이지가 표시됩니다. 이는 Slice URI를 사용하여 Slice 뷰어를 실행하는 것과 효과가 동일합니다.
Slice 목록을 보여주는 Slice 뷰어
다른 모드로 슬라이스 보기
Slice를 표시하는 앱은 런타임 시 SliceView#mode
를 수정할 수 있으므로 Slice가 각 모드에서 예상대로 표시되는지 확인해야 합니다.
모드를 변경하려면 페이지의 오른쪽 상단에서 메뉴 아이콘을 선택합니다.
모드가 '작게'로 설정된 단일 Slice 뷰어
첫 번째 Slice 만들기
Slice를 빌드하려면 Android 스튜디오 프로젝트를 열고 마우스 오른쪽 버튼으로 src
패키지를 클릭하고 New... > Other > Slice Provider를 선택합니다. 이렇게 하면 SliceProvider
를 확장하고 필요한 제공자 항목을 AndroidManifest.xml
에 추가하며 build.gradle
을 수정하여 필요한 Slice 종속 항목을 추가하는 클래스가 생성됩니다.
AndroidManifest.xml
의 수정사항은 다음과 같습니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.app"> ... <application> ... <provider android:name="MySliceProvider" android:authorities="com.example.android.app" android:exported="true" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.app.slice.category.SLICE" /> </intent-filter> </provider> ... </application> </manifest>
다음과 같은 종속 항목이 build.gradle
에 추가됩니다.
Kotlin
dependencies { // ... implementation "androidx.slice:slice-builders-ktx:(latest version)" // ... }
자바
dependencies { // ... implementation "androidx.slice:slice-builders:(latest version)" // ... }
각 Slice에는 연결된 URI가 있습니다. 표면에서는 Slice를 표시하려고 할 때 이 URI를 사용하여 앱에 결합 요청을 보냅니다. 그런 다음, 앱에서 이 요청을 처리하고 onBindSlice
메서드를 통해 동적으로 Slice를 빌드합니다. 그러면 표면에서 적절할 때 Slice를 표시할 수 있습니다.
다음은 /hello
URI 경로를 확인하고 Hello World Slice를 반환하는 onBindSlice
메서드의 예입니다.
Kotlin
override fun onBindSlice(sliceUri: Uri): Slice? { val activityAction = createActivityAction() return if (sliceUri.path == "/hello") { list(context, sliceUri, ListBuilder.INFINITY) { row { primaryAction = activityAction title = "Hello World." } } } else { list(context, sliceUri, ListBuilder.INFINITY) { row { primaryAction = activityAction title = "URI not recognized." } } } }
자바
@Override public Slice onBindSlice(Uri sliceUri) { if (getContext() == null) { return null; } SliceAction activityAction = createActivityAction(); ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY); // Create parent ListBuilder. if ("/hello".equals(sliceUri.getPath())) { listBuilder.addRow(new ListBuilder.RowBuilder() .setTitle("Hello World") .setPrimaryAction(activityAction) ); } else { listBuilder.addRow(new ListBuilder.RowBuilder() .setTitle("URI not recognized") .setPrimaryAction(activityAction) ); } return listBuilder.build(); }
위의 Slice 뷰어 섹션에서 만든 Slice 실행 구성을 사용하여 Hello World Slice의 Slice URI(예: slice-content://com.android.example.slicesample/hello
)를 전달하고 Slice 뷰어에서 확인합니다.
양방향 슬라이스
알림과 마찬가지로 사용자 상호작용에서 트리거되는 PendingIntent
객체를 첨부하여 Slice 내에서 클릭을 처리할 수 있습니다. 아래의 예에서는 이러한 인텐트를 수신하고 처리할 수 있는 Activity
를 시작합니다.
Kotlin
fun createSlice(sliceUri: Uri): Slice { val activityAction = createActivityAction() return list(context, sliceUri, INFINITY) { row { title = "Perform action in app" primaryAction = activityAction } } } fun createActivityAction(): SliceAction { val intent = Intent(context, MainActivity::class.java) return SliceAction.create( PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0), IconCompat.createWithResource(context, R.drawable.ic_home), ListBuilder.ICON_IMAGE, "Enter app" ) }
자바
public Slice createSlice(Uri sliceUri) { if (getContext() == null) { return null; } SliceAction activityAction = createActivityAction(); return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY) .addRow(new ListBuilder.RowBuilder() .setTitle("Perform action in app.") .setPrimaryAction(activityAction) ).build(); } public SliceAction createActivityAction() { if (getContext() == null) { return null; } return SliceAction.create( PendingIntent.getActivity( getContext(), 0, new Intent(getContext(), MainActivity.class), 0 ), IconCompat.createWithResource(getContext(), R.drawable.ic_home), ListBuilder.ICON_IMAGE, "Enter app" ); }
Slice는 또한 전환과 같이 앱으로 전송되는 인텐트에 상태가 포함된 다른 입력 유형도 지원합니다.
Kotlin
fun createBrightnessSlice(sliceUri: Uri): Slice { val toggleAction = SliceAction.createToggle( createToggleIntent(), "Toggle adaptive brightness", true ) return list(context, sliceUri, ListBuilder.INFINITY) { row { title = "Adaptive brightness" subtitle = "Optimizes brightness for available light" primaryAction = toggleAction } inputRange { inputAction = (brightnessPendingIntent) max = 100 value = 45 } } } fun createToggleIntent(): PendingIntent { val intent = Intent(context, MyBroadcastReceiver::class.java) return PendingIntent.getBroadcast(context, 0, intent, 0) }
자바
public Slice createBrightnessSlice(Uri sliceUri) { if (getContext() == null) { return null; } SliceAction toggleAction = SliceAction.createToggle( createToggleIntent(), "Toggle adaptive brightness", true ); ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY) .addRow(new ListBuilder.RowBuilder() .setTitle("Adaptive brightness") .setSubtitle("Optimizes brightness for available light.") .setPrimaryAction(toggleAction) ).addInputRange(new ListBuilder.InputRangeBuilder() .setInputAction(brightnessPendingIntent) .setMax(100) .setValue(45) ); return listBuilder.build(); } public PendingIntent createToggleIntent() { Intent intent = new Intent(getContext(), MyBroadcastReceiver.class); return PendingIntent.getBroadcast(getContext(), 0, intent, 0); }
그러면 다음과 같이 수신기에서 수신된 상태를 확인할 수 있습니다.
Kotlin
class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) { Toast.makeText(context, "Toggled: " + intent.getBooleanExtra( Slice.EXTRA_TOGGLE_STATE, false), Toast.LENGTH_LONG).show() } } companion object { const val EXTRA_MESSAGE = "message" } }
자바
public class MyBroadcastReceiver extends BroadcastReceiver { public static String EXTRA_MESSAGE = "message"; @Override public void onReceive(Context context, Intent intent) { if (intent.hasExtra(EXTRA_TOGGLE_STATE)) { Toast.makeText(context, "Toggled: " + intent.getBooleanExtra( EXTRA_TOGGLE_STATE, false), Toast.LENGTH_LONG).show(); } } }
동적 Slice
Slice에 동적 콘텐츠를 포함할 수도 있습니다. 다음 예에서는 이제 콘텐츠에 수신된 브로드캐트 수가 Slice에 포함됩니다.
Kotlin
fun createDynamicSlice(sliceUri: Uri): Slice { return when (sliceUri.path) { "/count" -> { val toastAndIncrementAction = SliceAction.create( createToastAndIncrementIntent("Item clicked."), actionIcon, ListBuilder.ICON_IMAGE, "Increment." ) list(context, sliceUri, ListBuilder.INFINITY) { row { primaryAction = toastAndIncrementAction title = "Count: ${MyBroadcastReceiver.receivedCount}" subtitle = "Click me" } } } else -> { list(context, sliceUri, ListBuilder.INFINITY) { row { primaryAction = createActivityAction() title = "URI not found." } } } } }
자바
public Slice createDynamicSlice(Uri sliceUri) { if (getContext() == null || sliceUri.getPath() == null) { return null; } ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY); switch (sliceUri.getPath()) { case "/count": SliceAction toastAndIncrementAction = SliceAction.create( createToastAndIncrementIntent("Item clicked."), actionIcon, ListBuilder.ICON_IMAGE, "Increment." ); listBuilder.addRow( new ListBuilder.RowBuilder() .setPrimaryAction(toastAndIncrementAction) .setTitle("Count: " + MyBroadcastReceiver.sReceivedCount) .setSubtitle("Click me") ); break; default: listBuilder.addRow( new ListBuilder.RowBuilder() .setPrimaryAction(createActivityAction()) .setTitle("URI not found.") ); break; } return listBuilder.build(); } public PendingIntent createToastAndIncrementIntent(String s) { Intent intent = new Intent(getContext(), MyBroadcastReceiver.class) .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s); return PendingIntent.getBroadcast(getContext(), 0, intent, 0); }
이 예에서는 수가 표시되지만 자체적으로 업데이트되지 않습니다. broadcast receiver를 수정하여 ContentResolver#notifyChange
를 사용해 시스템에 변경이 발생했음을 알릴 수 있습니다.
Kotlin
class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) { Toast.makeText( context, "Toggled: " + intent.getBooleanExtra( Slice.EXTRA_TOGGLE_STATE, false ), Toast.LENGTH_LONG ).show() receivedCount++; context.contentResolver.notifyChange(sliceUri, null) } } companion object { var receivedCount = 0 val sliceUri = Uri.parse("content://com.android.example.slicesample/count") const val EXTRA_MESSAGE = "message" } }
자바
public class MyBroadcastReceiver extends BroadcastReceiver { public static int sReceivedCount = 0; public static String EXTRA_MESSAGE = "message"; private static Uri sliceUri = Uri.parse("content://com.android.example.slicesample/count"); @Override public void onReceive(Context context, Intent intent) { if (intent.hasExtra(EXTRA_TOGGLE_STATE)) { Toast.makeText(context, "Toggled: " + intent.getBooleanExtra( EXTRA_TOGGLE_STATE, false), Toast.LENGTH_LONG).show(); sReceivedCount++; context.getContentResolver().notifyChange(sliceUri, null); } } }
템플릿
Slice는 다양한 템플릿을 지원합니다. 템플릿 옵션 및 동작에 관한 자세한 내용은 템플릿을 참조하세요.