联系人头像编辑保存过程

以新建联系人 —> 点击头像 —> 选择拍照 —> 设置头像为例

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为文件名
    }

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;
    }
}
View Code

 综上,头像保存了两份:一份是在数据库data表data15字段中的压缩数据byte[]——大小为96x96(较模糊)

                    一份是将头像文件直接写到了files/photos/rawContactId中——大小为720x720,数据库中保存了文件的大小和名称(即id)(较清晰)

原文地址:https://www.cnblogs.com/antoon/p/4423055.html