Android图片裁剪和上传服务器(含:拍照、图库选取)

使用

1
mContext.startActivity(new Intent(mContext, CropImageActivity.class));

权限声明

1
2
3
4
5
6
7
8
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

注册Activity

1
2
3
4
<activity
android:name=".CropImageActivity"
android:screenOrientation="portrait"
android:theme="@style/myTransparent" />

注册Provider

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${PACKAGE_NAME}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

透明的theme

1
2
3
4
5
6
7
8

<style name="myTransparent" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
<item name="android:windowBackground">@color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>

xml/file_paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- res/xml/file_paths -->

<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path
name="root"
path="" />

<files-path
name="files"
path="" />

<cache-path
name="cache"
path="" />

<external-path
name="external"
path="" />

<external-files-path
name="external_file"
path="" />

<external-cache-path
name="external_cache"
path="" />
</paths>

util/uri

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

/**
* 类:SystemProgramUtils 系统程序适配
* 1. 拍照
* 2. 相册
* 3. 裁切
* 作者: qxc
* 日期:2018/2/23.
*/
public class SystemProgramUtils {
public static final int REQUEST_CODE_PAIZHAO = 1;
public static final int REQUEST_CODE_ZHAOPIAN = 2;
public static final int REQUEST_CODE_CAIQIE = 3;

public static void paizhao(Activity activity, File outputFile) {
Intent intent = new Intent();
intent.setAction("android.media.action.IMAGE_CAPTURE");
intent.addCategory("android.intent.category.DEFAULT");
Uri uri = FileProviderUtils.uriFromFile(activity, outputFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
activity.startActivityForResult(intent, REQUEST_CODE_PAIZHAO);
}

public static void zhaopian(Activity activity) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction("android.intent.action.PICK");
intent.addCategory("android.intent.category.DEFAULT");
activity.startActivityForResult(intent, REQUEST_CODE_ZHAOPIAN);
}

public static void Caiqie(Activity activity, Uri uri, File outputFile, int width, int height) {
Intent intent = new Intent("com.android.camera.action.CROP");
FileProviderUtils.setIntentDataAndType(activity, intent, "image/*", uri, true);
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
//return-data为true时,直接返回bitmap,可能会很占内存,不建议,小米等个别机型会出异常!!!
//所以适配小米等个别机型,裁切后的图片,不能直接使用data返回,应使用uri指向
//裁切后保存的URI,不属于我们向外共享的,所以可以使用fill://类型的URI
Uri outputUri = Uri.fromFile(outputFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("return-data", false);

intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
activity.startActivityForResult(intent, REQUEST_CODE_CAIQIE);
}
}


/**
* 类:FileProviderUtils
* 从APP向外共享的文件URI时,必须使用该类进行适配,否则在7.0以上系统,会报错:FileUriExposedException(文件Uri暴露异常)
* 作者: qxc
* 日期:2018/2/23.
*/
public class FileProviderUtils {
/**
* 从文件获得URI
*
* @param activity 上下文
* @param file 文件
* @return 文件对应的URI
*/
public static Uri uriFromFile(Activity activity, File file) {
Uri fileUri;
//7.0以上进行适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String p = activity.getPackageName() + ".FileProvider";
fileUri = FileProvider.getUriForFile(
activity,
p,
file);
} else {
fileUri = Uri.fromFile(file);
}
return fileUri;
}

/**
* 设置Intent的data和类型,并赋予目标程序临时的URI读写权限
*
* @param activity 上下文
* @param intent 意图
* @param type 类型
* @param file 文件
* @param writeAble 是否赋予可写URI的权限
*/
public static void setIntentDataAndType(Activity activity,
Intent intent,
String type,
File file,
boolean writeAble) {
//7.0以上进行适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setDataAndType(uriFromFile(activity, file), type);
//临时赋予读写Uri的权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
}

/**
* 设置Intent的data和类型,并赋予目标程序临时的URI读写权限
*
* @param context 上下文
* @param intent 意图
* @param type 类型
* @param fileUri 文件uri
* @param writeAble 是否赋予可写URI的权限
*/
public static void setIntentDataAndType(Context context,
Intent intent,
String type,
Uri fileUri,
boolean writeAble) {
//7.0以上进行适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setDataAndType(fileUri, type);
//临时赋予读写Uri的权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (writeAble) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} else {
intent.setDataAndType(fileUri, type);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

// [Android 拍照,从图库选择照片,并裁剪,上传到服务器 - 简书](https://www.jianshu.com/p/bfd9fe0592cb)
// [拍照/从相册读取图片后进行裁剪的方法 - developer_Kale - 博客园](http://www.cnblogs.com/tianzhijiexian/p/3989296.html)
// [Android 7.0适配 -- FileProvider 拍照、选择相册、裁切图片, 小米机型适配 - 简书](https://www.jianshu.com/p/bec4497c2a63)

public class CropImageActivity extends Activity {
private static final String TAG = CropImageActivity.class.getSimpleName();
public static final String TMP_DIR = Environment.getExternalStorageDirectory() + "/renrenyoupin/tmp";
private int width;
private int height;
private File file;
private Activity mContext;
private static final int CAMERA_REQUEST_CODE = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;

if (savedInstanceState == null) {
initIconFile(true);

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
showDialog();
return;
}
requestPermission();
}
}

private void initIconFile(boolean remove) {
File tmpDir = new File(TMP_DIR);
if (!tmpDir.exists()) {
tmpDir.mkdirs();
}
file = new File(tmpDir.getAbsolutePath(), "icon.png");
if (remove && file.exists()) {
file.delete();
}
}


@TargetApi(Build.VERSION_CODES.M)
private void requestPermission() {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
showDialog();
} else {
//申请相机权限和STORAGE权限
ActivityCompat.requestPermissions(
mContext,
new String[]{Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
},
CAMERA_REQUEST_CODE);
}
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
&& grantResults[2] == PackageManager.PERMISSION_GRANTED) {
showDialog();
} else {
String msg = "没有相关运行权限";
//Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
backWithError(msg);
}
}
}

private void showDialog() {
Intent intent = getIntent();
width = intent.getIntExtra("width", 0);
height = intent.getIntExtra("height", 0);
if (width == 0) {
width = 350;
}
if (height == 0) {
height = 350;
}

new AlertDialog.Builder(this)
.setTitle("更换头像")
.setItems(new String[]{"拍照", "从相册选择",}, (dialog, which) -> {
switch (which) {
case 0://拍照
SystemProgramUtils.paizhao(mContext, file);
break;
case 1://从相册选择
SystemProgramUtils.zhaopian(mContext);
break;
}
})
.setOnCancelListener(dialog -> finish())
.create()
.show();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != SystemProgramUtils.REQUEST_CODE_PAIZHAO && data == null) {
finish();
return;
}

Bitmap bitmap;
Uri uri;
switch (requestCode) {
case SystemProgramUtils.REQUEST_CODE_PAIZHAO: // 从相机跳回来
if (resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}

if (file == null) {
initIconFile(false);
}
if (!file.exists()) {
backWithError("拍照异常");
return;
}
// 启动裁剪器
SystemProgramUtils.Caiqie(mContext, FileProviderUtils.uriFromFile(mContext, file), file, width, height);
break;
case SystemProgramUtils.REQUEST_CODE_ZHAOPIAN: // 从图库跳回来
// 此处的uri 是content类型的。 还有一种是file 型的。应该转换为后者
uri = data.getData();
if (uri == null) {
backWithError("图片无效");
return;
}
SystemProgramUtils.Caiqie(mContext, uri, file, width, height);
break;
case SystemProgramUtils.REQUEST_CODE_CAIQIE: // 从裁剪处跳回来
try {
uri = Uri.fromFile(file);
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
// 方法1
uri = saveBitmap(bitmap);
if (uri == null) {
backWithError("裁剪失败");
return;
}
sentImage(uri.getPath());
} catch (Exception ex) {
ex.printStackTrace();
}


break;
}
}

private void sentImage(String path) {
UokHttp.init().postImageFile(new ICallBack() {
@Override
public String getUrl() {
return APP_URL + "/android/user_center/person/edit_avatar";
}

@Override
public void doResponse(JSONObject responseInfo) {
backWithSuccess(responseInfo);
}

@Override
public void doError(String errorMsg) {
backWithError(errorMsg);
}
}, path);
}

private void backWithSuccess(JSONObject responseInfo) {
if (Rryp.instance != null && Rryp.instance.getCurrentCallbackContext() != null) {
Rryp.instance.getCurrentCallbackContext().success(responseInfo);
}
finish();
}

private void backWithError(String errorMsg) {
if (Rryp.instance != null && Rryp.instance.getCurrentCallbackContext() != null) {
Rryp.instance.getCurrentCallbackContext().error(errorMsg);
}
finish();
}

/**
* 把 Bitmap 保存在SD卡路径后,返回file 类型的 uri
*
* @param bm
* @return
*/
private Uri saveBitmap(Bitmap bm) {
if (bm == null) {
return null;
}

try {
FileOutputStream fos = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.PNG, 80, fos);
fos.flush();
fos.close();
return Uri.fromFile(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}

}

参考资料