본문 바로가기
프로그래밍/Android

Android 주소록 가져오기 (연락처 및 프로필 사진)

by Mr-후 2018. 11. 30.
반응형


Android 주소록 가져오기 (연락처 및 프로필 사진)



하이브리드 앱에서 네이티브 OEM의 연락처에 접근해서 선택된 주소록의 정보를 다시 웹으로 보내줘야 하는 기능을 개발하게 되었다. 급한건 아니라 하나씩 차근차근 진행을 하고 있는데 생각보다 어리버리 한 상태가 되었다. 아이폰의 경우는 보다 쉽게 구현을 했는데 안드로이드는 어떨지 아직 감을 못 잡겠다. 

아직 서툰 자바 코딩과 안드로이드에 대해서 잘 몰라 그런지 모르지만 복잡해도 너무 복잡해 보인다. 

이 포스팅을 쓰게 된 이유는 따른건 없고 주소록의 데이터를 로딩해서 해당 주소록의 프로필 사진을 로딩 하는 예제인데 다음 링크에서 거진 모든 소스를 참조했다. 그런데 이상한 현상이 있어 고민을 좀 했었는데 아직도 의문은 풀리지 않은 상태이다. 

참고(?)한 블로그 URL : http://mhwan.tistory.com/36 


우선 주소록을 가져오는 함수는 다음과 같다. 

public ArrayList<MyContacts> getContactList() {
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String[] projection = new String[] {
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
Contacts.PHOTO_ID,
Contacts._ID
};

String[] selectionArgs =null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";

Cursor cursor = getContentResolver().query(uri, projection, null,
selectionArgs, sortOrder);


LinkedHashSet<MyContacts> hasList = new LinkedHashSet<>();
ArrayList<MyContacts> contactsList;

if (cursor.moveToFirst()) {
do {
long photo_id = cursor.getLong(2);
long person = cursor.getLong(3);

MyContacts myContact = new MyContacts();
myContact.phone = cursor.getString(0);
myContact.fullName = cursor.getString(1);
myContact.thumnailId = photo_id;
myContact.personId = person;

if (myContact.phone.startsWith("01")) {
hasList.add(myContact);
//contactsList.add(myContact);
Log.d("<<CONTACTS>>", "name=" + myContact.fullName + ", phone=" + myContact.phone);
}

} while (cursor.moveToNext());
}

contactsList = new ArrayList<MyContacts>(hasList);
for (int i = 0; i < contactsList.size(); i++) {
contactsList.get(i).setId(i);
}

if (cursor != null) {
cursor.close();
}

return contactsList;
}

주소록의 사진을 가져오기 위해서 PHOTO_ID, _ID가 있어야 된다고 하는데 그래서 VO에 담았다. 


리스트는 RecyclerView 로 만들었고 Adapter는 Custom으로 만들었다. 

ViewHolder도 따로 만들었는데 ViewHolder의 경우는 리스트 셀의 재사용을 위해서 메모리 관리차원으로 사용하는 듯 하다. 

한번 로드된 리스트 position의 셀에 대해서는 재사용이 가능한 구조다. ViewHolder는 CustomAdapter의 onCreateViewHolder()에서 LayoutInflater되어진 뷰(리스트 UI)를 넘겨 받고 onBindViewHolder()에서 viewHolder를 받아 각각의 뷰에 값을 설정하는 방식이다. 




Adapter에서 각각 이름과 폰번호, 프로필 사진을 셋팅하는 소스다. 

public class ContactsRecyclerViewAdapter extends RecyclerView.Adapter<ContactsViewHolder> {
private ArrayList<MyContacts> list;
private final Context mContext;

public ContactsRecyclerViewAdapter(Context context, ArrayList<MyContacts> list) {
this.mContext = context;
this.list = list;
}

@Override
public ContactsViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.inc_listitem_contact, viewGroup, false);
return new ContactsViewHolder(view);
}

@Override
public void onBindViewHolder(ContactsViewHolder viewHolder, int position) {
MyContacts contacts = list.get(position);
viewHolder.name.setText(contacts.getFullName());
viewHolder.phone.setText(contacts.getPhone());
Log.d("<<CONTACT NAME>", contacts.getFullName());

viewHolder.profile.setImageDrawable(mContext.getResources().getDrawable(R.drawable.img_profile_thumnail));
Bitmap profile = loadContactPhoto(mContext.getContentResolver(),contacts.personId, contacts
.thumnailId);
if (profile != null) {
if (Build.VERSION.SDK_INT >= 21) {
viewHolder.profile.setBackground(new ShapeDrawable(new OvalShape()));
viewHolder.profile.setClipToOutline(true);
}
viewHolder.profile.setImageBitmap(profile);
} else {
// viewHolder.profile.setImageDrawable(mContext.getResources().getDrawable(R.drawable.img_profile_thumnail));
if (Build.VERSION.SDK_INT >= 21) {
viewHolder.profile.setClipToOutline(false);
}
}

}

@Override
public int getItemCount() {
return list.size();
}

public Bitmap loadContactPhoto(ContentResolver cr, long id, long photo_id) {
// Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
// InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(cr, uri);
// if (input != null)
// return resizingBitmap(BitmapFactory.decodeStream(input));
// else
// Log.d("<<CONTACT_PHOTO>>", "first try failed to load photo");

byte[] photoBytes = null;
Uri photoUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, photo_id);
Cursor c = cr.query(photoUri, new String[]{ContactsContract.CommonDataKinds.Photo.PHOTO},
null,null, null);
try {
if (c.moveToFirst())
photoBytes = c.getBlob(0);
} catch (Exception e) {
e.printStackTrace();
} finally {
c.close();
}

if (photoBytes != null) {
return resizingBitmap(BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length));
} else
Log.d("<<CONTACT_PHOTO>>", "second try also failed");

return null;

}

public Bitmap resizingBitmap(Bitmap oBitmap) {
if (oBitmap == null) {
return null;
}

float width = oBitmap.getWidth();
float height = oBitmap.getHeight();
float resizing_size = 120;

Bitmap rBitmap = null;
if (width > resizing_size) {
float mWidth = (float)(width / 100);
float fScale = (float)(resizing_size / mWidth);
width *= (fScale / 100);
height *= (fScale / 100);

} else if (height > resizing_size) {
float mHeight = (float)(height / 100);
float fScale = (float)(resizing_size / mHeight);

width *= (fScale / 100);
height *= (fScale / 100);
}

//Log.d("rBitmap : " + width + ", " + height);

rBitmap = Bitmap.createScaledBitmap(oBitmap, (int)width, (int)height, true);
return rBitmap;
}

}


나도 포스팅에서 본걸 그대로 옮겨 타이핑을 했는데 이상하게 리스트의 썸네일 이미지가 꼬이는 것이다. 

왜 그럴까? 알리가 없다. 왜냐면 제대로 이해를 하고 코딩을 한 것이 아니라서 그렇다. RecyclerView에 대해서는 개념을 잡고 있지만 ContentUris와 ContactsContract에 대해서는 아직 잘 모르겠다. 책에서도 너무 간다하게 설명하고 넘어간 부분들이라 고유한 URI을 가지고 접근하면 리소스를 획득할 수 있다고 하는데 그 큰 덩어리의 구조에 대해서 잘 모르기 때문에 아직은 시기상조. 




그래서 소스상에 주석으로 된 부분을 주석 처리하니 썸네일이 제대로 나왔는데 

//        Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
// InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(cr, uri);
// if (input != null)
// return resizingBitmap(BitmapFactory.decodeStream(input));
// else
// Log.d("<<CONTACT_PHOTO>>", "first try failed to load photo");

이 소스의 역할은 무엇인지 모르겠다. 스택오브플로우에도 저 구문에서 자구 null이 나온다는 말이 있었고 다음 구문으로 해결을 하면 된다는 답이 있었는데 

안드로이드 초보에겐 아직도 무슨 말인지 잘 모르는 말일 뿐. 

여튼 결론은 다음 소스로 했을 경우 아직 까지는 문제가 없다. 

byte[] photoBytes = null;
Uri photoUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, photo_id);
Cursor c = cr.query(photoUri, new String[]{ContactsContract.CommonDataKinds.Photo.PHOTO},
null,null, null);

ContentResolver에 대한 공부가 더 필요한 부분이다. 


반응형