스터디

[Manifest-Android] 11. 컨텐츠 프로바이더

참깨빵위에참깨빵_ 2025. 6. 23. 19:40
728x90
반응형

구조화된 데이터 집합에 대한 접근 관리, 앱 간 데이터 공유를 위한 표준화된 인터페이스를 제공하는 컴포넌트

다른 앱, 컴포넌트가 데이터 쿼리, 삽입, 업데이트, 삭제에 쓸 수 있는 중앙 저장소 역할을 해서 앱 간 안전하고 일관된 데이터 공유를 보장한다.

여러 앱에서 같은 데이터에 접근해야 하거나 DB, 내부 저장소 구조 노출 없이 다른 앱에 데이터를 줘야 할 경우 유용하다

 

컨텐츠 프로바이더의 목적

 

  • 데이터 접근 로직을 캡슐화해서 앱 간 데이터를 쉽고 안전하게 공유할 수 있게 한다
  • SQLite, 파일 시스템, 네트워크 기반 데이터 등 기본 데이터 소스를 추상화하고 데이터와 상호작용할 수 있는 통합 인터페이스를 제공한다

 

컨텐츠 프로바이더의 주요 구성요소

 

데이터 접근을 위해 URI를 사용하는데 URI는 아래 항목들로 구성된다.

 

  • 권한(authority) : 컨텐츠 공급자를 식별하는 데 사용. 보통 앱 패키지명 뒤에 ".provider"가 붙은 형태다
  • 경로(path) : 데이터 유형을 지정. /users 또는 /products 같은 형태일 수 있다
  • id : 옵셔널 값. 데이터 세트 안의 특정 아이템을 나타냄

 

구현 방법

 

ContentProvider를 상속하고 아래 함수들을 재정의해야 한다.

 

  • onCreate : 컨텐츠 프로바이더 초기화
  • query
  • insert
  • update
  • delete
  • getType : 데이터의 MIME 타입 리턴

 

class MyContentProvider : ContentProvider() {

    private lateinit var database: SQLiteDatabase

    override fun onCreate(): Boolean {
        database = MyDatabaseHelper(context!!).writableDatabase
        return true
    }

    override fun query(
        uri: Uri, 
        projection: Array<String>?, 
        selection: String?, 
        selectionArgs: Array<String>?, 
        sortOrder: String?
    ): Cursor? {
        return database.query("users", projection, selection, selectionArgs, null, null, sortOrder)
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val id = database.insert("users", null, values)
        return ContentUris.withAppendedId(uri, id)
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
        return database.update("users", values, selection, selectionArgs)
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        return database.delete("users", selection, selectionArgs)
    }

    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.dirvnd.com.example.myapp.users"
    }
}

 

컨텐츠 프로바이더 등록하기

 

다른 앱에서 내가 만든 컨텐츠 프로바이더에 접근할 수 있게 하려면 매니페스트에 선언해야 한다. authorities는 앞서 말한 대로 컨텐츠 프로바이더를 고유하게 식별하는 값이다.

 

<provider
   android:name=".MyContentProvider"
   android:authorities="com.example.myapp.provider"
   android:exported="true"
   android:grantUriPermissions="true" />

 

컨텐츠 프로바이더에서 데이터에 접근

 

ContentResolver를 써서 다른 앱의 컨텐츠 프로바이더와 상호작용할 수 있다. 이 클래스는 데이터 쿼리, 삽입, 업데이트, 삭제 함수를 제공한다.

 

val contentResolver = context.contentResolver

// Query data
val cursor = contentResolver.query(
    Uri.parse("content://com.example.myapp.provider/users"),
    null,
    null,
    null,
    null
)

// Insert data
val values = ContentValues().apply {
    put("name", "John Doe")
    put("email", "johndoe@example.com")
}

contentResolver.insert(Uri.parse("content://com.example.myapp.provider/users"), values)

 

컨텐츠 프로바이더 사용 사례는 아래와 같다.

 

  • 서로 다른 앱 간 데이터 공유, 앱 시작 프로세스 중 컴포넌트 or 리소스 초기화
  • 연락처, 미디어 파일, 앱 별 데이터 같은 구조화된 데이터에 대한 접근 제공
  • 연락처 앱 or 파일 선택기 같은 안드로이드 시스템 기능과의 통합 활성화
  • 세분화된 보안 제어를 통해 데이터 접근 허용

 

앱 시작 시 리소스, 구성 초기화를 위해 컨텐츠 프로바이더를 쓰는 사용 사례

 

컨텐츠 프로바이더의 다른 사용 사례는 앱 시작 시 리소스나 구성 초기화다. 일반적으로 이 작업들은 Application 클래스에서 수행하지만 이걸 별도의 컨텐츠 프로바이더에 캡슐화해서 문제(아마 관심사)를 더 잘 분리할 수 있다.

커스텀 컨텐츠 프로바이더를 만든 후 매니페스트에 등록하면 초기화 작업을 효율적으로 위임할 수 있다. 컨텐츠 프로바이더의 onCretae는 Application.onCreate보다 먼저 호출되기 때문에 첫 초기화를 위한 좋은 진입점이다.

 

파이어베이스 SDK는 커스텀 컨텐츠 프로바이더를 써서 SDK를 자동 초기화하는 게 예시다. 이렇게 하면 Application 클래스에선 FirebaseApp.initializeApp(this)를 직접 호출할 필요가 없다.

 

public class FirebaseInitProvider extends ContentProvider {
  /** Called before {@link Application#onCreate()}. */
  @Override
  public boolean onCreate() {
    try {
      currentlyInitializing.set(true);
      if (FirebaseApp.initializeApp(getContext()) == null) {
        Log.i(TAG, "FirebaseApp initialization unsuccessful");
      } else {
        Log.i(TAG, "FirebaseApp initialization successful");
      }
      return false;
    } finally {
      currentlyInitializing.set(false);
    }
  }
}

 

그리고 매니페스트에 등록한다.

 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!--Although the *SdkVersion is captured in gradle build files, this is required for non gradle builds -->
    <!--<uses-sdk android:minSdkVersion="21"/>-->
    <application>

        <provider
            android:name="com.google.firebase.provider.FirebaseInitProvider"
            android:authorities="${applicationId}.firebaseinitprovider"
            android:directBootAware="true"
            android:exported="false"
            android:initOrder="100" />
    </application>
</manifest>

 

이 패턴은 필수 리소스, 라이브러리를 앱 생명주기 초기에 자동 초기화시켜 더 깔끔하고 모듈화된 디자인을 제공한다.

다른 사용 사례는 앱 시작 시 컴포넌트를 초기화하는 방법을 제공하는 제트팩 App Startup 라이브러리다. 내부 구현에선 컨텐츠 프로바이더를 써서 initialize 인터페이스를 구현한 모든 사전 정의된 클래스를 초기화하는 InitializationProvider라는 클래스를 사용한다. 내부 구현은 아래와 같다.

 

/**
 * The {@link ContentProvider} which discovers {@link Initializer}s in an application and
 * initializes them before {@link Application#onCreate()}.
 */
public class InitializationProvider extends ContentProvider {

    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            Context applicationContext = context.getApplicationContext();
            if (applicationContext != null) {
                // Initialize all registered Initializer classes.
                AppInitializer.getInstance(context).discoverAndInitialize(getClass());
            } else {
               StartupLogger.w("Deferring initialization because `applicationContext` is null.");
            }
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }
}

 

반응형