국비교육(22-23)

84일차(1)/Android App(47) : Content Provider

서리/Seori 2023. 2. 8. 18:24

84일차(1)/Android App(47) : Content Provider

 

새 모듈 생성- Empty Activity

step20contentprovider

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/inputName"
        android:hint="검색할 이름 입력..."/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="연락처 정보 얻어오기"
        android:id="@+id/getBtn"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="false"
        android:gravity="top|left"
        android:id="@+id/console"/>

</LinearLayout>

 

- 이름을 입력하고 버튼을 누르면 휴대폰의 연락처 앱(contacts) 에서 연락처 불러오도록 하기

- 우리 앱에서 그 정보들을 가지고 올 수 있는지 권한 체크!

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 연락처 정보를 접근하겠다는 퍼미션 -->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

 

- manifest에 권한 관련 내용 추가 (permission)

- 하지만 이것만 있다고 승인되는건 아니고, 퍼미션을 유도하는 코딩이 필요하다.(사용자에게 권한 옵션 보여주기)

 

 

- 하지만 설정에 들어가보면 권한이 없다고 나온다.(Off 상태)

 

- 앱이 처음 설치되었을 때는 이 값이 '허용되지 않음'으로 나온다. 

- 그래서 이 권한을 확인하는 과정이 필요하다.

- 권한을 허용하게 만들어서 연락처 정보를 읽어와야 한다.

- 초반에는 manifest에 표기하기만 하면 권한이 승인됐는데, 사람들이 안읽고 승인하니까

  안전을 위해 운영체제에서 한번 더 승인하는 과정을 거치도록 한 것!

 

- 그럼 연락처는 어떻게 알아올 수 있을까?

- 컨텐츠 제공자로부터 데이터를 어떻게 얻어내는지 익힐 예정

→ Content Provider 의 사용방법을 익히는 것

 


 

 

** Content Provider

 

- 기기의 앱이 가지고 있는 정보(연락처 등) 을 어떤 앱에서 요청하면 제공해주는 역할

- Content Resolver 를 사용해서 Contacts App이 가지고 있는 데이터를 어떻게 얻어내는지 익히기

 

- 우리가 직접 Content Provider 를 만들 일은 거의 없다.

- 이것을 사용하는 사용자 입장에서 앱을 만들면 된다. Content Resolver를 잘 사용하면 된다!

 

- Content Provider를 하나의 DB처럼 사용할 수 있다.

- Content resolver를 사용해서 select 로 데이터를 뽑아오듯이 사용할 수 있다.

 

 

- 테스트를 위해 주소록에 연락처를 추가해둔다.

- Contacts 주소록 어플에 들어가서,

 

- 이렇게 여러개 만들어서 저장해준다.

 


 

MainActivity

package com.example.step20contentprovider;

import android.Manifest;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class MainActivity extends AppCompatActivity {
    //필요한 필드 정의하기
    EditText inputName, console;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //EditText 객체의 참조값 얻어내서 필드에 저장하기
        inputName=findViewById(R.id.inputName);
        console=findViewById(R.id.console);

        Button getBtn=findViewById(R.id.getBtn);
        //연락처 정보 얻어오기 버튼을 눌렀을 때
        getBtn.setOnClickListener(v -> {
            //연락처 정보 얻어오기 권한이 체크되었는지 상수값 얻어오기
            int permissionCheck= ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS);

            if(permissionCheck != PackageManager.PERMISSION_GRANTED){
                //권한이 필요한 목록을 배열에 담는다.
                String[] permissions={Manifest.permission.READ_CONTACTS};
                //배열을 전달해서 해당 권한을 부여하도록 요청한다.
                ActivityCompat.requestPermissions(this,
                        permissions,
                        0); //요청의 아이디
                return; //메소드는 여기서 종료
            }
            //연락처 정보 얻어오기
            getContacts();
        });
    }
    //퍼미션 요청의 결과가 전달되는 메소드 재정의하기
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        
        switch (requestCode){
            case 0: //0번 요청인 경우
                //권한을 부여했다면
                if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    //연락처 정보 얻어오기
                    getContacts();
                }else{//권한을 부여 하지 않았다면
                    Toast.makeText(this, "연락처 접근 권한이 필요합니다.", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
    //ContentProvider 로부터 ContentResolver 객체를 이용해서 연락처 정보를 얻어내는 메소드
    public void getContacts(){
        //입력한 검색어
        String keyword=inputName.getText().toString();
        //ContentResolver 객체의 참조값을 얻어오기
        ContentResolver resolver=getContentResolver();
        //연락처 정보를 지칭할 수 있는 Uri 객체 얻어내기
        Uri contactUri= ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        //select할 칼럼
        String[] columns={
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
                ContactsContract.CommonDataKinds.Phone.NUMBER,
                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
        };
        //where display_name like '%키워드%'
        String where=ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" LIKE ? ";
        //order by contact_id asc
        String order=ContactsContract.CommonDataKinds.Phone.CONTACT_ID+" ASC";
        //?에 바인딩할 인자
        String[] args = {"%"+keyword+"%"};
        //원하는 정보를 얻어낸다.
        Cursor cursor=resolver.query(contactUri,  //table name
                columns,  //column name
                where,  //where
                args,  //selection args
                order);  //order by
        while (cursor.moveToNext()){
                int id=(int)cursor.getLong(0);
                String phoneNumber=cursor.getString(1);
                String name=cursor.getString(2);
                //결과를 한줄의 문자열로 구성해서
                String result=id+" | "+phoneNumber+" | "+name;
                console.append(result+"\n");

        }
    }
}

 

- step19에서 권한을 얻어오는 작업에서, 전화거는 것으로 되어있는 상수를 바꾸어주면 된다.

 (CALL_PHONE → READ_CONTACTS)

 

- 아래 이 메소드에서 반응을 본다. 이것은 액티비티가 가지고있는 메소드이다.

- 사용자가 어떤 권한을 켰는지 또는 켜지 않았는지 여기서 조사할 수 있다.

 

 

- 요청의 코드 번호를 아래에 인자로 전달하고, 인자로 배열(string[ ], int[ ])도 전달한다.

 

- 이 권한이 허가된 것의 값은 0이다. static final 상수로 정의되어 있다.(PERMISSION_GRANTED)

- 결과값이 0이 나오면 permission이 승인된 것이다.

 


 

 

- contacts의 배열을 가져올 예정이다.

- java에서 배열은 { } 이므로 저렇게 표시한다!

 

- permission 승인 얻은 경우(초록) / 얻지 못한 경우(빨강)

 

- if문 대신 switch 코드로 작성해보았다.

- if문 안에 if문을 또 넣어서 사용하면 코드가 지저분해질 수 있어서... 이런식으로 활용하면 좋다!

 

- getContentResolver() 를 쓰면 리턴타입으로 ContentResolver 타입이 나온다.

 이렇게 액티비티의 메소드로부터 받아내면 된다.

- Intent 객체처럼 우리가 직접 객체를 생성할 필요는 없다.

 

- 쿼리 메소드 사용! 인자를 5개 받는다.

 

- SELECT 문을 작성한다고 생각하면 된다.

- 어디서 가지고 올 것인지(파랑), 

  무엇을 가지고 올 것인지(분홍),

  어떤 조건으로 가지고 올 것인지(초록)

- 조건에 들어가는 값은 배열로 전달한다.( String[ ] )

 

 

- 정렬은 마지막 인자로 넣어주면 된다.

- SELECT 문이라고 생각하고 사용하면 된다.

 

ContactsContract.CommonDataKinds.Phone.CONTENT_URI;

- 연락처 정보를 저장할 Uri 객체를 얻어낸다.

 

- 여기 작성한 이 코드는 이 sql문과 같다.

 

- 이 ?에 값을 바인딩하려면 '%xxx%' 으로 넣어주면 된다.

 

 

SELECT contact_id, number, display_name
FROM CONTENT_URI
WHERE dispaly_name LIKE '%xxx%'
ORDER BY contact_id ASC

 

- 최종적으로 이런 sql문으로 작성된다고 볼 수 있다.

- content privider를 사용해서 마치 SELECT문을 수행하듯이 작성한다.

 

- query() 메소드 안에 5개의 정보를 전달한다.

 

 

- WHERE 절을 명시하지 않으면 모든 내용을 다 가져온다.

- 값이 동적으로 들어간다면 args 인자에 전달한다.

 (만약 ?가 여러개라면 string 배열에 순서대로 넣는다.)

 


 

- Cursor 객체에서 데이터 빼내기

- Cursor는 ResultSet이라고 생각하면 된다.

 

 

- 아래 columnIndex에 들어간 0 1 2 는 각각 이것이다

- ID primary key는 long타입인데, int타입으로 받으면 된다.

 

 

- EditText에 console 필드 추가

- cursor로 가져온 값을 개행기호와 함께 console에 출력해주기!

 

- checkPermission 창이 뜬다

 

 

- 허용하면 이렇게 연락처가 출력된다.

 

- where 조건과 selection 인자를 모두 null로 넣어주면

 가지고 있는 모든 연락처 목록이 출력된다.

 

 

 

- 이메일 값은 이렇게 따로 얻어내야 한다. phone과 같이 얻어낼 수는 없다.