시작하기

이 페이지에서는 앱에서 환경을 설정하고 Slice를 빌드하는 방법을 보여줍니다.

참고: Android 스튜디오 3.2 이상에는 Slice 개발에 도움이 되는 추가 도구와 기능이 포함되어 있습니다.

  • AndroidX 리팩터링 도구: AndroidX 라이브러리를 사용하는 프로젝트에서 작업하는 경우 필요합니다.
  • Slice 린트 검사: Slice를 빌드할 때 규정에 어긋나는 일반적인 오류를 포착합니다.
  • SliceProvider 템플릿: SliceProvider를 빌드할 때 상용구를 처리합니다.

Slice 뷰어 다운로드 및 설치

SliceView API를 구현하지 않고 Slice를 테스트하는 데 사용할 수 있는 최신 샘플 Slice 뷰어 APK 버전을 다운로드하세요.

개발자 환경에서 ADB가 제대로 설정되지 않았다면 자세한 내용은 ADB 가이드를 참조하세요.

다운로드된 slice-viewer.apk와 동일한 디렉터리에서 다음 명령어를 실행하여 기기에 Slice 뷰어를 설치하세요.

adb install -r -t slice-viewer.apk

Slice 뷰어 실행

Slice 뷰어는 Android 스튜디오 프로젝트 또는 명령줄에서 실행할 수 있습니다.

Android 스튜디오 프로젝트에서 Slice 뷰어 실행

  1. 프로젝트에서 Run > Edit Configurations...를 선택합니다.
  2. 왼쪽 상단에서 녹색 더하기 기호를 클릭합니다.
  3. Android 앱 선택

  4. Name 입력란에 Slice를 입력합니다.

  5. Module 드롭다운에서 앱 모듈을 선택합니다.

  6. Launch Options 아래 Launch 드롭다운에서 URL을 선택합니다.

  7. URL 입력란에 slice-<your slice URI>를 입력합니다.

    예: slice-content://com.example.your.sliceuri

  8. 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 보기

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)"
// ...
}

Java

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."
            }
        }
    }
}

Java

@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 뷰어에서 확인합니다.

양방향 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"
    )
}

Java

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)
}

Java

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"
    }
}

Java

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."
                }
            }
        }
    }
}

Java

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"
    }
}

Java

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는 다양한 템플릿을 지원합니다. 템플릿 옵션 및 동작에 관한 자세한 내용은 템플릿을 참조하세요.