以新建联系人 —> 点击头像 —> 选择拍照 —> 设置头像为例。
com.android.contacts.editor.PhotoActionPopup中处理菜单选择 public static ListPopupWindow createPopupMenu(Context context, View anchorView, final Listener listener, int mode) {
...... final OnItemClickListener clickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final ChoiceListItem choice = choices.get(position);
switch (choice.getId()) {
......
case ChoiceListItem.ID_TAKE_PHOTO:
listener.onTakePhotoChosen(); break;
......
}
UiClosables.closeQuietly(listPopupWindow);
}
}
......
listPopupWindow.setOnItemClickListener(clickListener);
...... }
listener调用onTakePhotoChosen();
package com.android.contacts.detail; public abstract class PhotoSelectionHandler implements OnClickListener {
......
public abstract class PhotoActionListener implements PhotoActionPopup.Listener {
......
public void onTakePhotoChosen() {
try {
// Launch camera to take photo for selected contact
startTakePhotoActivity(mTempPhotoUri);//mTempPhotoUri是缓存目录
} catch (ActivityNotFoundException e) {
Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
}
}
}
private void startTakePhotoActivity(Uri photoUri) { final Intent intent = getTakePhotoIntent(photoUri); startPhotoActivity(intent, REQUEST_CODE_CAMERA_WITH_DATA, photoUri); }
}
startPhotoActivity具体实现由ContactEditorFragment中的PhotoHandler处理
package com.android.contacts.editor; public class ContactEditorFragment extends Fragment implements SplitContactConfirmationDialogFragment.Listener,
AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener, RawContactReadOnlyEditorView.Listener { ......
private final class PhotoHandler extends PhotoSelectionHandler { ...... @Override public void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) { mRawContactIdRequestingPhoto = mEditor.getRawContactId(); mCurrentPhotoHandler = this; mStatus = Status.SUB_ACTIVITY; mCurrentPhotoUri = photoUri; ContactEditorFragment.this.startActivityForResult(intent, requestCode); } }
......
public void onActivityResult(int requestCode, int resultCode, Intent data) { ...... // See if the photo selection handler handles this result. if (mCurrentPhotoHandler != null && mCurrentPhotoHandler.handlePhotoActivityResult( requestCode, resultCode, data)) { return; } ...... }
}
PhotoSelectionHandler调用handlePhotoActivityResult
com.android.contacts.detail.PhotoSelectionHandler public boolean handlePhotoActivityResult(int requestCode, int resultCode, Intent data) { final PhotoActionListener listener = getListener(); if (resultCode == Activity.RESULT_OK) { switch (requestCode) { ...... case REQUEST_CODE_CAMERA_WITH_DATA: final Uri uri; boolean isWritable = false; if (data != null && data.getData() != null) { uri = data.getData();//拿到返回的图片uri } else { uri = listener.getCurrentPhotoUri(); isWritable = true; } final Uri toCrop; if (isWritable) { // Since this uri belongs to our file provider, we know that it is writable // by us. This means that we don't have to save it into another temporary // location just to be able to crop it. toCrop = uri; } else { toCrop = mTempPhotoUri;//使用缓存路径 try { ContactPhotoUtils.savePhotoFromUriToUri(mContext, uri, toCrop, false);//将图片写到缓存目录 } catch (SecurityException e) { Log.d(TAG, "Did not have read-access to uri : " + uri); return false; } } doCropPhoto(toCrop, mCroppedPhotoUri);//进入Gallery实现图片切割 return true; } } return false; }
/** * Sends a newly acquired photo to Gallery for cropping */ private void doCropPhoto(Uri inputUri, Uri outputUri) { try { // Launch gallery to crop the photo final Intent intent = getCropImageIntent(inputUri, outputUri);//Gallery中会将裁剪图片写入到outputUri——即mCroppedPhotoUri startPhotoActivity(intent, REQUEST_CROP_PHOTO, inputUri); } catch (Exception e) { Log.e(TAG, "Cannot crop image", e); Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); } }
再次进入ContactEditorFragment中的PhotoHandler调用startPhotoActivity,然后再次返回到onActivityResult,触发PhotoSelectionHandler调用handlePhotoActivityResult
public boolean handlePhotoActivityResult(int requestCode, int resultCode, Intent data) { final PhotoActionListener listener = getListener(); if (resultCode == Activity.RESULT_OK) { switch (requestCode) { // Cropped photo was returned case REQUEST_CROP_PHOTO: { final Uri uri; if (data != null && data.getData() != null) { uri = data.getData();//获取裁剪后的uri } else { uri = mCroppedPhotoUri;//如果没有返回,直接使用mCroppedPhotoUri,Gallery中已经进行了写入操作 } try { // delete the original temporary photo if it exists mContext.getContentResolver().delete(mTempPhotoUri, null, null);//删除不用的缓存目录 listener.onPhotoSelected(uri); return true; } catch (FileNotFoundException e) { return false; } }
ContactEditorFragment中的PhotoHandler处理onPhotoSelected
public void onPhotoSelected(Uri uri) throws FileNotFoundException { final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(mContext, uri);//通过uri获取Bitmap setPhoto(mRawContactId, bitmap, uri);//用bitmap显示头像,而后转为byte[]传给RawContacDelta,准备写入数据库 mCurrentPhotoHandler = null; bindEditors(); }
ContactEditorFragment调用setPhoto
private void setPhoto(long rawContact, Bitmap photo, Uri photoUri) { BaseRawContactEditorView requestingEditor = getRawContactEditorView(rawContact); if (photo == null || photo.getHeight() < 0 || photo.getWidth() < 0) { // This is unexpected. Log.w(TAG, "Invalid bitmap passed to setPhoto()"); } if (requestingEditor != null) { requestingEditor.setPhotoBitmap(photo);//显示头像,并将bitmap压缩为96x96大小,转为byte[],传给ValuesDelta } else { Log.w(TAG, "The contact that requested the photo is no longer present."); } mUpdatedPhotos.putParcelable(String.valueOf(rawContact), photoUri);//将头像uri保存到Bundle——mUpdatedPhotos中,key为Contact id mRawContactIdPhoto = String.valueOf(rawContact);//如果是新增联系人, id = -1 }
public abstract class BaseRawContactEditorView extends LinearLayout { public void setPhotoBitmap(Bitmap bitmap) { mPhoto.setPhotoBitmap(bitmap); } } public class PhotoEditorView extends LinearLayout implements Editor { public void setPhotoBitmap(Bitmap photo) { if (photo == null) { // Clear any existing photo and return mEntry.put(Photo.PHOTO, (byte[])null); resetDefault(); return; } mPhotoImageView.setImageBitmap(photo); mFrameView.setEnabled(isEnabled()); mHasSetPhoto = true; mEntry.setFromTemplate(false); // When the user chooses a new photo mark it as super primary mEntry.setSuperPrimary(true); // Even though high-res photos cannot be saved by passing them via // an EntityDeltaList (since they cause the Bundle size limit to be // exceeded), we still pass a low-res thumbnail. This simplifies // code all over the place, because we don't have to test whether // there is a change in EITHER the delta-list OR a changed photo... // this way, there is always a change in the delta-list. final int size = ContactsUtils.getThumbnailSize(getContext());//从ContentProvider2中获取系统设定的图片大小,默认为96 final Bitmap scaled = Bitmap.createScaledBitmap(photo, size, size, false);//将头像bitmap压缩为96x96大小 final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);//将压缩的bitmap转为二进制byte[] if (compressed != null) mEntry.setPhoto(compressed); //private ValuesDelta mEntry;
package com.android.contacts.common.model; public class ValuesDelta implements Parcelable { public void setPhoto(byte[] value) { put(ContactsContract.CommonDataKinds.Photo.PHOTO, value);//将byte[]放到data15字段中,(Photo.PHOTO = “data15”) } public void put(String key, byte[] value) { ensureUpdate(); mAfter.put(key, value);//public ContentValues mAfter; } private void ensureUpdate() { if (mAfter == null) { mAfter = new ContentValues(); } } }
}
}
到这里,头像压缩数据byte[]保存到了data15, 头像原始Uri保存到了Bundle——mUpdatedPhotos中。
-----------------------------------------最后保存过程--------------------------------
ContactEditorFragment调用save,启动ContactSaveService
public boolean save(int saveMode) { ...... // Save contact Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState, SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(), ((Activity)mContext).getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos); mContext.startService(intent); // Don't try to save the same photos twice. mUpdatedPhotos = new Bundle(); return true; }
ContactSaveService处理
private void saveContact(Intent intent) { ...... //先保存RawContactDelta中对应的数据库各字段数据,包括data15字段中的压缩头像数据 //RawContactDelta数据保存完毕后,处理头像uri if (updatedPhotos != null) { ...... if (!saveUpdatedPhoto(rawContactId, photoUri)) { succeeded = false; } ...... } }
private boolean saveUpdatedPhoto(long rawContactId, Uri photoUri) { final Uri outputUri = Uri.withAppendedPath( ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), RawContacts.DisplayPhoto.CONTENT_DIRECTORY);//= content://com.android.contacts/raw_contacts/rawContactId/display_photo //手机中的目录:/data/data/com.android.providers.contacts/files/photos/rawContactId return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true);//将头像文件写到outputUri目录中, rawContactId为文件名 }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
package com.android.contacts.util; public class ContactPhotoUtils { /** * Given an input photo stored in a uri, save it to a destination uri */ public static boolean savePhotoFromUriToUri(Context context, Uri inputUri, Uri outputUri, boolean deleteAfterSave) { FileOutputStream outputStream = null; InputStream inputStream = null; try { outputStream = context.getContentResolver() .openAssetFileDescriptor(outputUri, "rw").createOutputStream(); inputStream = context.getContentResolver().openInputStream( inputUri); final byte[] buffer = new byte[16 * 1024]; int length; int totalLength = 0; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); totalLength += length; } Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + inputUri.toString()); } catch (IOException e) { Log.e(TAG, "Failed to write photo: " + inputUri.toString() + " because: " + e); return false; } finally { Closeables.closeQuietly(inputStream); Closeables.closeQuietly(outputStream); if (deleteAfterSave) { context.getContentResolver().delete(inputUri, null, null); } } return true; } }
综上,头像保存了两份:一份是在数据库data表data15字段中的压缩数据byte[]——大小为96x96(较模糊)
一份是将头像文件直接写到了files/photos/rawContactId中——大小为720x720,数据库中保存了文件的大小和名称(即id)(较清晰)