https://survivalcoding.com/p/android_basic
위 서적을 참고하였습니다.
이번 포스트에선 콘텐트 프로바이더(ContentProvider)에 대해 알아보겠습니다. 콘텐트 프로바이더는 안드로이드 4대 컴포넌트 중 하나인데요, 앱 내부의 데이터베이스나 파일을 외부로 공개하는 역할을 합니다.
카카오톡이 휴대폰 전화번호를 가져와서 친구 추가를 할 수 있는 이유는 콘텐트 프로바이더가 이러한 정보들을 공개를 해놨기 때문입니다. 그래서 콘텐트 프로바이더는 어떤 파일을 가져와주는 중간 매개체 역할을 하는 것이라고 보면 이해가 될 것입니다. 콘텐트 프로바이더가 가져온 자료들을 보여주기 위해 이번 포스트에선 커서 어댑터(Cursor Adapter)를 사용할 것입니다.
다음은 안드로이드에서 기본으로 제공하는 대표 URI입니다. 권한이 필요한 경우 AndroidManifests.xml 파일에 권한을 추가하셔야 합니다.
권한 불필요 | |
내부 사진 | MediaStore.Images.Media.INTERNAL_CONTENT_URI |
내부 동영상 | MediaStore.Video.Media.INTERNAL_CONTENT_URI |
내부 동영상 섬네일 | MediaStore.Video.Thumbnails.INTERNAL_CONTENT_URI |
내부 음악 | MediaStore.Audio.Media.INTERNAL_CONTENT_URI |
내부 음악앨범 | MediaStore.Audio.Albums.INTERNAL_CONTENT_URI |
[android.permission.READ_EXTERNAL_STORAGE] 권한 필요 | |
외부 사진 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI |
외부 동영상 | MediaStore.Video.Media.EXTERNAL_CONTENT_URI |
외부 동영상 섬네일 | MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI |
외부 음악 | MideaStore.Audio.Media.EXTERNAL_CONTENT_URI |
외부 음악 앨범 | MediaStore.Albums.EXTERNAL_CONTENT_URI |
[android.permission.READ_CONTACTS] 권한 필요 | |
통화 이력 | CallLog.Calls.CONTENT_URI |
연락처 | ContactsContract.Contacts.CONTENT_URI |
연락처의 주소 | ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI |
연락처의 메일 | ContactsContract.CommonDataKinds.Email.CONTENT_URI |
연락처의 그룹 | ContactsContract.Groups.CONTENT_URI |
단말 설정 | Settings.System.CONTENT_URI |
보안 설정 | Settings.Secure.CONTENT_URI |
[android.permissionREAD_USER_DICTIONARY] 권한 필요 | |
사용자 정보 | UserDictionary.Words.CONTENT_URI |
[com.android.browser.permission.READ_HISTORY_BOOKMARKS] 또는 [com.android.browser.permission.WRITE_HISTORY_BOOKMARKS] 권한 필요 | |
북마크 | Browser.BOOKMARKS_URI |
검색 문자열 | Browser.SEARCHES_URI |
이제 실습 진행하겠습니다. 이번에 만들어 볼 것은 갤러리 내에 있는 사진들을 전부 가져와서 앱에서 볼 수 있고, 사진을 터치하면 다른 액티비티로 보여지는 앱을 만들겠습니다.
먼저 진행하기 전 app 수준의 build.gradle 파일에 가서 앱에 필요한 외부 라이브러리들을 불러와 줍니다. 저희는 다음 2개의 외부 라이브러리를 사용하겠습니다.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// 외부 라이브러리
implementation 'gun0912.ted:tedpermission:2.1.1'
implementation 'com.github.bumptech.glide:glide:4.11.0'
}
Glide : 구글에서 개발한 이미지 로딩 라이브러리입니다. 요즘 폰은 성능이 어느 정도 뒷바침이 돼서 예전보단 이미지 로딩하는데 메모리를 너무 많이 차지해서 렉이 걸리거나, 심한 경우 앱이 강제 종료되는 경우가 있습니다. 이를 방지하기 위해 나온 것이 Glide입니다. Glide를 사용하면 이미지의 캐시나 비동기 이미지 로딩 등을 쉽게 해결할 수 있습니다.
이것과 별개로 Picasso라는 라이브러리도 있습니다. 내용은 같지만 Glide는 용량이 적은 이미지, Picasso는 용량이 큰 이미지 처리를 할 때 주로 사용된다고 합니다.
TedPermission : TedPermission은 여러 가지의 권한들을 손 쉽게 처리할 수 있는 라이브러리입니다. 앱을 개발하다 보면 많고 다양한 권한을 부여할 때가 있는데, 이때 TedPermission을 사용하면 몇 줄 안 되는 코드로 다양한 권한을 처리할 수 있습니다.
이제 레이아웃 구성을 하겠습니다.
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"
android:orientation="vertical"
tools:context=".MainActivity">
</LinearLayout>
activity_main.xml은 아무것도 없습니다. 이는 권한을 추가하기 위해 일부러 비워놓은 것입니다. 물론 MainActivity에도 권한 추가관련 소스 말곤 없겠죠?
item_photo.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/photo_item"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:contentDescription="Load to image in phone" />
</LinearLayout>
여기선 나중에 나올 GridView에 보일 이미지뷰를 생성했습니다.
draw_image.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<GridView
android:id="@+id/photo_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2"
tools:listitem="@layout/item_photo"/>
</LinearLayout>
GridView를 통해 이미지를 격자 형식으로 출력할 것입니다.
draw_big_image.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<GridView
android:id="@+id/photo_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2"
tools:listitem="@layout/item_photo"/>
</LinearLayout>
GirdView의 ImageView를 터치하면 이미지를 draw_big_image.xml에서 나오게 합니다. 출력은 다이얼로그 형식으로 출력을 해보겠습니다.
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- 다이얼로그 타이틀 없애기 -->
<style name="DialogTheme" parent="Theme.AppCompat.Light.Dialog">
<item name="windowNoTitle">true</item>
</style>
</resources>
draw_big_image.xml을 다이얼로그 형식으로 출력하기 위해 parent를 "Theme.AppCompat.Liget.Dialg"로 지정했습니다. 그리고 액티비티 출력 시 타이틀 바가 출력되는 것을 막기 위해 "windowNoTitle" 속성도 추가했습니다.
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionListener permissionListener = new PermissionListener() {
@Override
public void onPermissionGranted() {
Toast.makeText(MainActivity.this, "권한 허용", Toast.LENGTH_SHORT).show();
startActivity(new Intent(MainActivity.this, DrawImage.class));
finish();
}
@Override
public void onPermissionDenied(ArrayList<String> deniedPermissions) {
Toast.makeText(MainActivity.this, "권한 거부\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
finish();
}
};
TedPermission.with(this)
.setPermissionListener(permissionListener)
.setRationaleMessage("권한을 허용해야 앱 실행이 가능합니다.")
.setDeniedMessage("권한을 허용해야 앱 실행이 가능합니다.")
.setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.check();
}
}
MainActivity에선 TedPermission으로 권한들을 설정하고 있습니다.
DrawImage.java
public class DrawImage extends AppCompatActivity {
private MyCursorAdapter adapter;
private Intent intent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.draw_image);
GridView gridView = (GridView) findViewById(R.id.photo_list);
Cursor cursor = getContentResolver().query //Cursor는 DB를 담을 수 있는 객체. 흔히 액셀의 행과 열의 데이터로 저장
(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, //From (무슨 정보에 접근하려는지의 대상), EXTERNAL_CONTENT_URI : 이미지 가리키는 대표 URI
null, //Select
null, //Where
null, //Where
MediaStore.Images.ImageColumns.DATE_TAKEN+ " DESC");
adapter = new MyCursorAdapter(this, cursor);
gridView.setAdapter(adapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) parent.getAdapter().getItem(position);
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
Toast.makeText(DrawImage.this, "사진경로 : " +path, Toast.LENGTH_SHORT).show();
intent = new Intent(DrawImage.this, DrawBigImage.class);
intent.putExtra("path", path);
startActivity(intent);
}
});
}
}
DrawImage에선 Cursor Adapter를 통해 GridView에 사진을 지정하는 소스입니다.
MyCursorAdapter.java
public class MyCursorAdapter extends CursorAdapter {
public MyCursorAdapter(Context context, Cursor c) {
super(context, c, false);
}
//맨 처음 layout을 생성하는 메소드
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.item_photo, parent, false);
}
//데이터를 뷰에 바인딩하여 화면에 표시하는 메소드 (view는 아이템의 레이아웃, cursor는 아이템의 데이터
@Override
public void bindView(View view, Context context, Cursor cursor) {
//ViewHolder holder = (ViewHolder) view.getTag();
ImageView imageView = (ImageView) view.findViewById(R.id.photo_item);
//사진 경로 가져오기
String uri = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
//holder.imageView.setImageURI(Uri.parse(uri));
//사진 이미지 뷰에 표시
Glide.with(context).load(uri).placeholder(R.drawable.ic_launcher_foreground).into(imageView);
}
}
Cursor Adapter 클래스입니다. 마지막에 Glide를 사용하는 것을 볼 수 있습니다.
DrawBigImage.java
public class DrawBigImage extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.draw_big_image);
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int width = (int) (display.getWidth() * 0.8); //Display 사이즈의 80%
int height = (int) (display.getHeight() * 0.8); //Display 사이즈의 80%
getWindow().getAttributes().width = width;
getWindow().getAttributes().height = height;
Intent intent = getIntent();
ImageView imageView = (ImageView) findViewById(R.id.big_image);
String path = intent.getStringExtra("path");
imageView.setImageURI(Uri.parse(path));
Glide.with(this).load(path).placeholder(R.drawable.ic_launcher_foreground).into(imageView);
}
}
GridView에 있는 ImageView 터치 시 이미지를 확장시킬 클래스입니다. 전에 말했듯이 다이얼로그 형식으로 출력을 하기 위해 AndroidManifests.xml 파일로 가서 DrawBigImage 클래스에 한 가지 속성을 추가합니다.
AndroidManifests.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="hello.world.study">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:requestLegacyExternalStorage="true"
tools:targetApi="q">
<activity android:name="hello.DrawBigImage"
<!-- 다이얼로그 테마 추가 -->
android:theme="@style/DialogTheme">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="hello.DrawImage" />
</application>
</manifest>
DrawBigImage 클래스 속성에 theme 속성을 추가했습니다. 이제 저 액티비티는 다이얼로그 형식으로 출력될 것입니다.
다음은 결과물입니다.
개인 정보때문에 가린 것이 좀 많은데 제대로 출력되는 것을 볼 수 있습니다.
근데 사진 출력이 안 되는 분이 있을 수 있습니다. 이는 안드로이드 Q 이상 기기부터는 내부 파일을 제한을 걸어뒀기 때문에 작동을 하지 않는 것입니다. AndroidManifests에 다음 코드를 추가하셔야 정상적으로 작동합니다.
<application android:requestLegacyExternalStorage="true" </application>
그럼 정상적으로 작동하는 것을 볼 수 있을 겁니다.
감사합니다.
'Android > Android' 카테고리의 다른 글
[Android] AsyncTask와 카운트다운 타이머 앱 (0) | 2020.07.30 |
---|---|
[Android] 스레드와 핸들러 (0) | 2020.07.29 |
[Android] 안드로이드 앱 개발을 위한 필요한 자바 문법 (0) | 2020.06.24 |
[Android] 브로드캐스트 리시버 (Broadcast Receiver) (0) | 2020.06.16 |
[Android] 뷰페이저로 좌우 밀리는 화면 만들기 (0) | 2020.06.16 |