๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๋นˆ ๊ตฌ๋ฉ ์ฑ„์šฐ๊ธฐ

[Android] ์•ˆ๋“œ๋กœ์ด๋“œ SDK์— ํฌํ•จ๋œ ์–ด๋…ธํ…Œ์ด์…˜

์ถœ์ฒ˜

ChatGPT


1. @RequiresPermission

  • ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋‚˜ ํด๋ž˜์Šค๊ฐ€ ํ˜ธ์ถœ๋  ๋–„ ํ•„์š”ํ•œ ๊ถŒํ•œ์„ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ช…์‹œ์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‹ค์ˆ˜๋กœ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
  • ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ์ฝ๋Š” ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์ด ๋ฉ”์„œ๋“œ๋‚˜ ํด๋ž˜์Šค๊ฐ€ ํŠน์ • ๊ถŒํ•œ์„ ์š”๊ตฌํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…ํ™•ํžˆ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ธ๋‹ค.
  • ์•ˆ๋“œ๋กœ์ด๋“œ Lint ๋„๊ตฌ๋Š” ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ๊ถŒํ•œ์ด ์ ์ ˆํ•˜๊ฒŒ ์š”์ฒญ๋˜๊ณ  ์žˆ๋Š” ๊ฒ€์‚ฌํ•œ๋‹ค. 
  • ๋„์ž… ์‹œ๊ธฐ : API ๋ ˆ๋ฒจ 16 (Jelly Bean)๋ถ€ํ„ฐ.
import android.Manifest
import android.content.Context
import android.net.wifi.WifiManager
import androidx.annotation.RequiresPermission

class WifiController(private val context: Context) {

    private val wifiManager: WifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager

    @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE)
    fun isWifiEnabled(): Boolean {
        return wifiManager.isWifiEnabled
    }

    @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE)
    fun setWifiEnabled(enabled: Boolean) {
        wifiManager.isWifiEnabled = enabled
    }
}

 

์ด ์ฝ”๋“œ์—์„œ๋Š” ACCESS_WIFI_STATE์™€ CHANGE_WIFI_STATE ๊ถŒํ•œ์ด ๊ฐ๊ฐ ํ•„์š”ํ•จ์„ ๋ช…์‹œํ•˜๊ณ  ์žˆ๋‹ค.

 

2. @Nullable ๋ฐ @NonNull

  • @Nullable: ํ•ด๋‹น ๋ณ€์ˆ˜, ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ๊ฐ’, ๋˜๋Š” ๋ฉ”์„œ๋“œ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ null์ผ ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • @NonNull: ํ•ด๋‹น ์š”์†Œ๊ฐ€ null์ด ๋  ์ˆ˜ ์—†์Œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • ๋‘ ์–ด๋…ธํ…Œ์ด์…˜์€ ์ž๋ฐ”์—์„œ ์œ ์šฉํ•˜๋‹ค. ์ฝ”ํ‹€๋ฆฐ์€ Null-Safety๋ฅผ ์–ธ์–ด ์ˆ˜์ค€์—์„œ ์ง€์›ํ•œ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: ์ดˆ๊ธฐ ์•ˆ๋“œ๋กœ์ด๋“œ ๋ฒ„์ „๋ถ€ํ„ฐ ์กด์žฌํ–ˆ์ง€๋งŒ, API ๋ ˆ๋ฒจ 19 (KitKat) ์ดํ›„๋กœ ์•ˆ๋“œ๋กœ์ด๋“œ SDK์—์„œ ๊ณต์‹ ์ง€์›๋˜์—ˆ๋‹ค.
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class UserManager {

    // ์ด ๋ฉ”์„œ๋“œ๋Š” ์ ˆ๋Œ€ null์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ์„ ๋ช…์‹œ
    @NonNull
    public String getUserName() {
        return "John Doe";
    }

    // ์ด ๋ฉ”์„œ๋“œ๋Š” null์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹œ
    @Nullable
    public String getUserNickname() {
        // ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณ„๋ช…์ด ์—†์„ ์ˆ˜๋„ ์žˆ์Œ
        return null;
    }

    // ์ด ๋ฉ”์„œ๋“œ๋Š” null์ด ๋  ์ˆ˜ ์—†๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์•ผ ํ•จ์„ ๋ช…์‹œ
    public void printUserName(@NonNull String userName) {
        System.out.println("User name: " + userName);
    }

    // ์ด ๋ฉ”์„œ๋“œ๋Š” null์ด ๋  ์ˆ˜ ์žˆ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ
    public void printUserNickname(@Nullable String userNickname) {
        if (userNickname != null) {
            System.out.println("User nickname: " + userNickname);
        } else {
            System.out.println("User has no nickname");
        }
    }

    public static void main(String[] args) {
        UserManager userManager = new UserManager();

        // NonNull ๋ฐ˜ํ™˜๊ฐ’์€ null ์ฒดํฌ๊ฐ€ ํ•„์š” ์—†์Œ
        String userName = userManager.getUserName();
        userManager.printUserName(userName);

        // Nullable ๋ฐ˜ํ™˜๊ฐ’์€ null ์ฒดํฌ๊ฐ€ ํ•„์š”ํ•จ
        String userNickname = userManager.getUserNickname();
        userManager.printUserNickname(userNickname);

        // NonNull ํŒŒ๋ผ๋ฏธํ„ฐ์— null์„ ์ „๋‹ฌํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ปดํŒŒ์ผ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
        // userManager.printUserName(null); // ์ปดํŒŒ์ผ ์˜ค๋ฅ˜

        // Nullable ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” null์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Œ
        userManager.printUserNickname(null);
    }
}

 

 

  1. @NonNull:
    • getUserName() ๋ฉ”์„œ๋“œ๋Š” ์ ˆ๋Œ€ null์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•œ๋‹ค.
    • printUserName() ๋ฉ”์„œ๋“œ๋Š” null์ด ์•„๋‹Œ ๊ฐ’์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…์‹œํ•œ๋‹ค.
  2. @Nullable:
    • getUserNickname() ๋ฉ”์„œ๋“œ๋Š” null์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๊ณ , ์‚ฌ์šฉ์ž๋Š” ๋ฐ˜ํ™˜ ๊ฐ’์„ ์‚ฌ์šฉํ•  ๋•Œ ๋ฐ˜๋“œ์‹œ null ์ฒดํฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.
    • printUserNickname() ๋ฉ”์„œ๋“œ๋Š” null์ผ ์ˆ˜ ์žˆ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  3. ๋ฉ”์ธ ๋ฉ”์„œ๋“œ:
    • getUserName()์˜ ๋ฐ˜ํ™˜๊ฐ’์€ NonNull๋กœ ์ง€์ •๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ณ„๋„์˜ null ์ฒดํฌ ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฐ˜๋ฉด getUserNickname()์€ Nullable๋กœ ์ง€์ •๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ˜ํ™˜๋œ ๊ฐ’์ด null์ผ ๊ฐ€๋Šฅ์„ฑ์„ ๊ณ ๋ คํ•ด null ์ฒดํฌ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.

 

 

3. @UiThread, @MainThread, @WorkerThread

  • @UiThread: ๋ฉ”์„œ๋“œ๊ฐ€ UI ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • @MainThread: ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค (์ฃผ๋กœ UI ์Šค๋ ˆ๋“œ๋ฅผ ์˜๋ฏธํ•œ๋‹ค).
  • @WorkerThread: ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 21 (Lollipop)๋ถ€ํ„ฐ.
import android.os.AsyncTask
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread

class DataFetcher {

    @WorkerThread
    fun fetchData(): String {
        // ์ด ๋ฉ”์„œ๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•จ
        Thread.sleep(1000) // ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        return "Data"
    }

    @UiThread
    fun displayData(data: String) {
        // ์ด ๋ฉ”์„œ๋“œ๋Š” UI ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•จ
        println("Displaying data: $data")
    }

    fun startFetching() {
        AsyncTask.execute {
            val data = fetchData() // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
            displayData(data) // UI ์Šค๋ ˆ๋“œ์—์„œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ
        }
    }
}

 

์—ฌ๊ธฐ์„œ๋Š” @WorkerThread์™€ @UiThread๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ๊ฐ์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์–ด๋””์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ๋ช…์‹œํ•œ๋‹ค.

 

4. @CheckResult

  • @CheckResult: ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜๋“œ์‹œ ํ™•์ธํ•ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š์œผ๋ฉด ์ค‘์š”ํ•œ ๋™์ž‘์„ ๋†“์น  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒฝ๊ณ ๋ฅผ Lint๋ฅผ ํ†ตํ•ด ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 17 (Jelly Bean MR1)๋ถ€ํ„ฐ.
import androidx.annotation.CheckResult

@CheckResult
fun calculateSum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    val result = calculateSum(3, 4) // ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ
    println("Sum: $result")

    // calculateSum(3, 4) // ์ด ์ค„์—์„œ Lint๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฒฝ๊ณ ํ•  ๊ฒƒ
}

 

@CheckResult๋Š” ๋ฐ˜ํ™˜๊ฐ’์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์„ ๋•Œ ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•ด ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ.

 

5. @VisibleForTesting

  • @VisibleForTesting: ํŠน์ • ๋ฉ”์„œ๋“œ, ํ•„๋“œ ๋˜๋Š” ํด๋ž˜์Šค๊ฐ€ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” private์ด์ง€๋งŒ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 21 (Lollipop)๋ถ€ํ„ฐ.
import androidx.annotation.VisibleForTesting

class Calculator {

    fun add(a: Int, b: Int): Int {
        return a + b
    }

    @VisibleForTesting
    internal fun subtract(a: Int, b: Int): Int {
        return a - b
    }
}

 

subtract ๋ฉ”์„œ๋“œ๋Š” ์ฃผ๋กœ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋งŒ ๊ณต๊ฐœ๋˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” internal๋กœ ์œ ์ง€๋œ๋‹ค.

 

  • ์™œ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ @VisibleForTesting ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ subtract() ๋ฉ”์„œ๋“œ์— internal ์ ‘๊ทผ ์ œํ•œ์ž๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์ธ๊ฐ€
๋”๋ณด๊ธฐ
๋”๋ณด๊ธฐ

์™œ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ @VisibleForTesting ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ subtract() ๋ฉ”์„œ๋“œ์— internal ์ ‘๊ทผ ์ œํ•œ์ž๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒƒ์ธ๊ฐ€

 

@VisibleForTesting์™€ internal์˜ ์—ญํ• 

  • @VisibleForTesting: ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ๋ฉ”์„œ๋“œ๋‚˜ ํ•„๋“œ๊ฐ€ ์›๋ž˜๋Š” ์ œํ•œ๋œ ์ ‘๊ทผ ์ˆ˜์ค€(์˜ˆ: private, internal)์„ ๊ฐ€์ง€์ง€๋งŒ, ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ ค๋Š” ์˜๋„๋ฅผ ๋ช…์‹œํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ์ฆ‰, ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ "์ด ์ฝ”๋“œ๋Š” ์›๋ž˜๋Š” ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ๋ง์•„์•ผ ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค"๋Š” ์˜๋„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.
  • internal ์ ‘๊ทผ ์ œํ•œ์ž: ์ฝ”ํ‹€๋ฆฐ์—์„œ internal์€ ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ์ ‘๊ทผ ์ œํ•œ์ž๋‹ค. ๋”ฐ๋ผ์„œ internal๋กœ ์„ ์–ธ๋œ ๋ฉ”์„œ๋“œ๋‚˜ ํ•„๋“œ๋Š” ๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์˜ ๋‹ค๋ฅธ ํด๋ž˜์Šค๋‚˜ ํ•จ์ˆ˜์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ชจ๋“ˆ ์™ธ๋ถ€์—์„œ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

 

์˜ˆ์ œ ์ฝ”๋“œ์—์„œ subtract()์— internal์„ ์‚ฌ์šฉํ•œ ์ด์œ 

class Calculator {

    fun add(a: Int, b: Int): Int {
        return a + b
    }

    @VisibleForTesting
    internal fun subtract(a: Int, b: Int): Int {
        return a - b
    }
}

 

์ด ์ฝ”๋“œ์—์„œ subtract() ๋ฉ”์„œ๋“œ๊ฐ€ internal๋กœ ์„ ์–ธ๋œ ์ด์œ ๋Š” ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์—์„œ๋Š” ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ.

 

1. ๋ชจ๋“ˆ์˜ ์บก์Šํ™” ์œ ์ง€

  • subtract() ๋ฉ”์„œ๋“œ๋Š” ๋ณธ๋ž˜ private์œผ๋กœ ๋‘์–ด์•ผ ํ•  ๋ฉ”์„œ๋“œ์ผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ๋Š” ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด๋•Œ @VisibleForTesting ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ๋งŒ ์กด์žฌํ•œ๋‹ค๊ณ  ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • ๋™์‹œ์— internal์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์—์„œ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œํ•ด, ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์—์„œ๋Š” ์ด ๋ฉ”์„œ๋“œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค. ์ด๋Š” ๋ชจ๋“ˆ์˜ ์บก์Šํ™”๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ๋„, ํ…Œ์ŠคํŠธ์˜ ํŽธ์˜์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

2. ๋ชจ๋“ˆ ์™ธ๋ถ€์—์„œ์˜ ์ ‘๊ทผ ๋ฐฉ์ง€

  • ๋งŒ์•ฝ subtract() ๋ฉ”์„œ๋“œ๋ฅผ public์œผ๋กœ ์„ ์–ธํ•˜๋ฉด, ์ด ๋ฉ”์„œ๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๋ถ€๋ถ„์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉ๋˜๊ธฐ๋ฅผ ์›ํ•  ๊ฒฝ์šฐ, public์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋ถ€์ ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค.
  • internal๋กœ ์ œํ•œํ•˜๋ฉด, ์ด ๋ฉ”์„œ๋“œ๋Š” ๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ณ  ์™ธ๋ถ€์—์„œ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜๋ฏ€๋กœ, ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

internal๋กœ ์œ ์ง€ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ

๋งŒ์•ฝ subtract() ๋ฉ”์„œ๋“œ๊ฐ€ ํ…Œ์ŠคํŠธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ํด๋ž˜์Šค๋‚˜ ๋ชจ๋“ˆ์—์„œ๋„ ์‚ฌ์šฉ๋˜๊ธธ ์›ํ•œ๋‹ค๋ฉด public์œผ๋กœ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ชจ๋“  ์ฝ”๋“œ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์ง€์ง€๋งŒ, ์ด ๊ฒฝ์šฐ์—๋Š” @VisibleForTesting์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชฉ์ ์ด ํฌ์„๋  ์ˆ˜ ์žˆ์–ด๋‹ค

๊ทธ๋Ÿฌ๋‚˜ ์˜ˆ์ œ์—์„œ๋Š” ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ๋งŒ ๋…ธ์ถœ๋˜๊ธฐ๋ฅผ ์›ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, internal๋กœ ์ œํ•œ์„ ๋‘์–ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ์—์„œ๋Š” ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋„๋ก ํ•œ ๊ฒƒ์ด.

 

๊ฒฐ๋ก 

  • @VisibleForTesting์€ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์›๋ž˜๋Š” ์ œํ•œ๋œ ์ ‘๊ทผ ์ˆ˜์ค€์„ ๊ฐ€์ง„ ๋ฉ”์„œ๋“œ๋ฅผ ๋…ธ์ถœํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
  • internal์€ ๋ชจ๋“ˆ์˜ ๊ฒฝ๊ณ„๋ฅผ ๋„˜์ง€ ์•Š๋Š” ์„ ์—์„œ๋งŒ ๋ฉ”์„œ๋“œ๊ฐ€ ์‚ฌ์šฉ๋˜๋„๋ก ์ œํ•œํ•ด, ๋ชจ๋“ˆ์˜ ์บก์Šํ™”๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.
  • ์ด ์ ‘๊ทผ๋ฒ•์€ ์ฝ”๋“œ์˜ ์•ˆ์ „์„ฑ์„ ๋†’์ด๊ณ , ์˜๋„์น˜ ์•Š์€ ์‚ฌ์šฉ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋œ๋‹ค.

 

6. @CallSuper

  • @CallSuper: ์˜ค๋ฒ„๋ผ์ด๋“œ๋œ ๋ฉ”์„œ๋“œ์—์„œ ๋ฐ˜๋“œ์‹œ ์Šˆํผ ํด๋ž˜์Šค์˜ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋™์ž‘์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 21 (Lollipop)๋ถ€ํ„ฐ.
import androidx.annotation.CallSuper

open class Animal {
    @CallSuper
    open fun makeSound() {
        println("Animal sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        super.makeSound() // ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•ด์•ผ ํ•จ
        println("Bark")
    }
}

 

์—ฌ๊ธฐ์„œ๋Š” @CallSuper๋ฅผ ์‚ฌ์šฉํ•ด ์„œ๋ธŒํด๋ž˜์Šค๊ฐ€ ๋ถ€๋ชจ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•˜๋„๋ก ๋ช…์‹œํ–ˆ๋‹ค.

 

7. @Size

  • @Size: ์ปฌ๋ ‰์…˜, ๋ฐฐ์—ด, ๋ฌธ์ž์—ด ๋“ฑ์˜ ํฌ๊ธฐ ๋˜๋Š” ๊ธธ์ด์— ๋Œ€ํ•œ ์ œํ•œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ๋ฐฐ์—ด์˜ ํฌ๊ธฐ๊ฐ€ 10์ด์–ด์•ผ ํ•จ์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 21 (Lollipop)๋ถ€ํ„ฐ.
import androidx.annotation.Size

fun setScores(@Size(min = 1, max = 5) scores: IntArray) {
    println("Scores: ${scores.joinToString()}")
}

fun main() {
    setScores(intArrayOf(10, 20, 30)) // ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ
    // setScores(intArrayOf()) // Lint๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•จ (ํฌ๊ธฐ๊ฐ€ 1 ์ด์ƒ์ด์–ด์•ผ ํ•จ)
}

 

@Size๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐฐ์—ด์˜ ํฌ๊ธฐ์— ๋Œ€ํ•œ ์ œ์•ฝ์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

 

8. @IntDef ๋ฐ @StringDef

  • @IntDef: ํŠน์ • ๋ฉ”์„œ๋“œ ๋˜๋Š” ๋ณ€์ˆ˜์— ํ—ˆ์šฉ๋˜๋Š” ์ •์ˆ˜ ๊ฐ’์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๊ฒƒ์€ ์—ด๊ฑฐํ˜•์˜ ๋Œ€์•ˆ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด, ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๊ธฐ๋„ ํ•œ๋‹ค.
  • @StringDef: ํŠน์ • ๋ฉ”์„œ๋“œ ๋˜๋Š” ๋ณ€์ˆ˜์— ํ—ˆ์šฉ๋˜๋Š” ๋ฌธ์ž์—ด ๊ฐ’์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•œ๋‹ค. ๋ฌธ์ž์—ด ์ƒ์ˆ˜์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ธฐ ์œ„ํ•ด ๋„์ž…๋๋‹ค. ์ž˜๋ชป๋œ ๋ฌธ์ž์—ด ๊ฐ’์˜ ์‚ฌ์šฉ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 19 (KitKat)๋ถ€ํ„ฐ.
import androidx.annotation.IntDef

const val MODE_A = 0
const val MODE_B = 1

@IntDef(MODE_A, MODE_B)
@Retention(AnnotationRetention.SOURCE)
annotation class Mode

fun setMode(@Mode mode: Int) {
    println("Mode set to $mode")
}

fun main() {
    setMode(MODE_A) // ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ
    // setMode(2) // ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ž˜๋ชป๋œ ๊ฐ’์ด๋ผ๊ณ  ๊ฒฝ๊ณ ํ•จ
}

 

@IntDef๋Š” ํ—ˆ์šฉ๋˜๋Š” ์ •์ˆ˜ ๊ฐ’์„ ์ œํ•œํ•ด ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋†’์—ฌ์ค€๋‹ค.

 

import androidx.annotation.StringDef;

public class NetworkManager {

    public static final String WIFI = "WIFI";
    public static final String MOBILE = "MOBILE";
    public static final String ETHERNET = "ETHERNET";

    @StringDef({WIFI, MOBILE, ETHERNET})
    @Retention(RetentionPolicy.SOURCE)
    public @interface NetworkType {}

    private String currentNetworkType = WIFI;

    public void setNetworkType(@NetworkType String networkType) {
        this.currentNetworkType = networkType;
    }

    @NetworkType
    public String getNetworkType() {
        return currentNetworkType;
    }
}

 

9. @Keep

  • @Keep: ํ”„๋กœ๊ฐ€๋“œ(ProGuard)๋‚˜ R8 ๊ฐ™์€ ์ฝ”๋“œ ์••์ถ•๊ธฐ์—์„œ ํด๋ž˜์Šค, ๋ฉ”์„œ๋“œ, ํ•„๋“œ๊ฐ€ ์ œ๊ฑฐ๋˜๊ฑฐ๋‚˜ ๋‚œ๋…ํ™”๋˜์ง€ ์•Š๋„๋ก ๋ณดํ˜ธํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 21 (Lollipop)๋ถ€ํ„ฐ.
import androidx.annotation.Keep

@Keep
class ImportantClass {
    fun doSomething() {
        println("Doing something important")
    }
}

 

@Keep์„ ์‚ฌ์šฉํ•˜๋ฉด ProGuard๋‚˜ R8์ด ์ด ํด๋ž˜์Šค์™€ ๊ทธ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ๋‚œ๋…ํ™”ํ•˜์ง€ ์•Š๋„๋ก ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

10. @RequiresApi

  • @RequiresApi: ๋ฉ”์„œ๋“œ๋‚˜ ํด๋ž˜์Šค๊ฐ€ ํŠน์ • API ๋ ˆ๋ฒจ ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 19 (KitKat)๋ถ€ํ„ฐ.
import android.os.Build
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.O)
fun useNewApiFeature() {
    // Android O (API 26) ์ด์ƒ์—์„œ๋งŒ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ
    println("Using Android O feature")
}

 

@RequiresApi๋Š” ํŠน์ • API ๋ ˆ๋ฒจ ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋‚˜ ํด๋ž˜์Šค๋ฅผ ๋ช…์‹œํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

 

11. @TargetApi

  • ํ•ด๋‹น ๋ฉ”์„œ๋“œ, ํด๋ž˜์Šค, ๋˜๋Š” ํ•„๋“œ๊ฐ€ ํŠน์ • API ๋ ˆ๋ฒจ์—์„œ๋งŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Œ์„ ๋ฌธ์„œํ™”ํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ๋ฆฌ๋ทฐ์–ด์™€ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋ฉ”์„œ๋“œ๊ฐ€ ์˜๋„๋œ API ๋ ˆ๋ฒจ์„ ๋ช…ํ™•ํžˆ ์•Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.
  • IDE์—์„œ๋Š” @TargetApi ์• ๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด, ์‚ฌ์šฉ ์ค‘์ธ API ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๊ฒฝ๊ณ ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ.
  • ๋„์ž… ์‹œ๊ธฐ: API ๋ ˆ๋ฒจ 16 (Jelly Bean)๋ถ€ํ„ฐ.
import android.annotation.TargetApi
import android.os.Build
import android.widget.Toast
import android.content.Context

class ApiLevelExample(private val context: Context) {

    // ์ด ๋ฉ”์„œ๋“œ๋Š” API ๋ ˆ๋ฒจ 21 (Lollipop) ์ด์ƒ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์„ค๊ณ„๋จ
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    fun showToastWithNewFeature() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // API ๋ ˆ๋ฒจ 21 ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ
            Toast.makeText(context, "This feature is available on API 21 and above", Toast.LENGTH_LONG).show()
        } else {
            // API ๋ ˆ๋ฒจ 21 ์ดํ•˜์—์„œ๋„ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ
            Toast.makeText(context, "This feature is not available on this API level", Toast.LENGTH_LONG).show()
        }
    }
}

 

 

๋Ÿฐํƒ€์ž„์—์„œ ์‹ค์ œ๋กœ API ๋ ˆ๋ฒจ์„ ํ™•์ธํ•˜๊ณ , ํ•ด๋‹น ๋ ˆ๋ฒจ์—์„œ๋งŒ ํŠน์ • ๊ธฐ๋Šฅ์ด ์‹คํ–‰๋˜๋„๋ก ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ์ด ์–ด๋…ธํ…Œ์ด์…˜ ์ž์ฒด๊ฐ€ ๋Ÿฐํƒ€์ž„์—์„œ ์ฝ”๋“œ์˜ ์‹คํ–‰์„ ์ œ์–ดํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.

  1. @TargetApi ์–ด๋…ธํ…Œ์ด์…˜
    • ์ด ์–ด๋…ธํ…Œ์ด์…˜์€ ๋ฉ”์„œ๋“œ์— ๋ถ™์—ฌ์„œ, ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ํŠน์ • API ๋ ˆ๋ฒจ ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  2. Build.VERSION_CODES.LOLLIPOP:
    • Build.VERSION_CODES.LOLLIPOP์€ API ๋ ˆ๋ฒจ 21์„ ์˜๋ฏธํ•œ๋‹ค.
    • ์ด ์ƒ์ˆ˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ SDK์—์„œ ์ œ๊ณตํ•˜๋ฉฐ, ํ˜„์žฌ ๊ธฐ๊ธฐ์˜ API ๋ ˆ๋ฒจ๊ณผ ๋น„๊ตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
  3. showToastWithNewFeature() ๋ฉ”์„œ๋“œ:
    • ์ด ๋ฉ”์„œ๋“œ๋Š” API ๋ ˆ๋ฒจ 21 ์ด์ƒ์˜ ๊ธฐ๊ธฐ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
    • API ๋ ˆ๋ฒจ 21 ์ดํ•˜์—์„œ๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆฌ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.