本文收集了项目开发中遇到的大大小小的问题,主要目的也是方便以后的开发能够别踩那么多的坑、提高开发效率。
一、Glide裁剪成圆形图片
使用Glide自带的功能即可
RequestOptions requestOptions = RequestOptions.circleCropTransform();
Glide.with(getActivity())
.load(resId)
.placeholder(R.drawable.placeholder_pic)
.apply(requestOptions)//重点是这一句
.into(iv_avatar);
二、Resources中的getColor(int)已过时
替换成ContextCompat.getColor(Context,R.color.colorAccent);
//例如
iv_avatar_bg.setBackgroundColor(ContextCompat.getColor(getActivity(),R.color.themeColor));
三、动态申请权限时,onRequestPermissionsResult未被回调
动态申请权限时,我们要注意动态申请权限的地方在哪里。这里的地方指的是Activity或者是Fragment。
如果是Activity:申请的方法应该用ActivityCompat.requestPermissions;
如果是Fragment:申请的方法应该用requestPermissions。
四、拍照或者从相册中选择的图片被旋转
整体思路就是获取图片被旋转的角度之后,再旋转相应的角度即可
4.1 拍照:ContentUri转文件路径(通过反射)
//获取content开头的Uri文件在文件系统中的路径
public static String getFPUriToPath(Context context, Uri uri) {
try {
List packs = context.getPackageManager().getInstalledPackages(PackageManager.GET_PROVIDERS);
if (packs != null) {
String fileProviderClassName = FileProvider.class.getName();
for (PackageInfo pack : packs) {
ProviderInfo[] providers = pack.providers;
if (providers != null) {
for (ProviderInfo provider : providers) {
if (uri.getAuthority().equals(provider.authority)) {
if (provider.name.equalsIgnoreCase(fileProviderClassName)) {
Class fileProviderClass = FileProvider.class;
try {
Method getPathStrategy = fileProviderClass.getDeclaredMethod("getPathStrategy", Context.class, String.class);
getPathStrategy.setAccessible(true);
Object invoke = getPathStrategy.invoke(null, context, uri.getAuthority());
if (invoke != null) {
String PathStrategyStringClass = FileProvider.class.getName() + "$PathStrategy";
Class> PathStrategy = Class.forName(PathStrategyStringClass);
Method getFileForUri = PathStrategy.getDeclaredMethod("getFileForUri", Uri.class);
getFileForUri.setAccessible(true);
Object invoke1 = getFileForUri.invoke(invoke, uri);
if (invoke1 instanceof File) {
String filePath = ((File) invoke1).getAbsolutePath();
return filePath;
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
break;
}
break;
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
4.2 从相册中选择:ContentUri转文件路径
public static String getRealPathFromURI(Context context, Uri uri) {
int sdkVersion = Build.VERSION.SDK_INT;
if (sdkVersion >= 19) { // api >= 19
return getRealPathFromUriAboveApi19(context, uri);
} else { // api < 19
return getRealPathFromUriBelowAPI19(context, uri);
}
}
private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
String[] proj = {MediaStore.Audio.Media.DATA};
Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(proj[0]);
String picturePath = cursor.getString(columnIndex);
cursor.close();
return picturePath;
}
/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
String filePath = null;
if ("content".equalsIgnoreCase(uri.getScheme())){
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
Log.e(TAG,"real path = "+filePath) ;
return filePath;
}
/**
* 获取数据库表中的 _data 列,即返回Uri对应的文件路径
* @return
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
String path = null;
String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
path = cursor.getString(columnIndex);
}
} catch (Exception e) {
if (cursor != null) {
cursor.close();
}
}
return path;
}
4.3 获取被旋转的角度以及旋转图片
//读取图片旋转角度
public static int readPictureDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
Log.i(TAG,"readPictureDegree : orientation = " + orientation);
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
degree = 90;
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
degree = 180;
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
degree = 270;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
//旋转图片
public static Bitmap rotateBitmap(int angle, Bitmap bitmap) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
Bitmap rotation = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
matrix, true);
return rotation;
}
五、没有使用音频却出现AudioTrack错误
在logcat中的报错信息如下,项目中没有使用任何音频,却出现了AudioTrack错误:
此时如果定位不到任何地方的代码时,可以尝试在Run项中查看有没有其他报错信息。该次的错误其实是textView.setText时里面的参数放进了int类型的数据,所以才导致了错误。
六、recyclerview.setLayoutManager空指针错误
androidx.recyclerview.widget.RecyclerView.setLayoutManager on a null object reference
在排除了recyclerview包名相同,语法正确之后,依然出现空指针错误。这时可以改一下xml中recyclerview的id,然后再重新运行即可。
七、toast自带包名
//toast日常使用方式改为:
Toast toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
toast.setText("请填写相关信息!");
toast.show();
八、启动页白屏
白屏产生的原因我就不讲了,无非是application在初始化时耗时较久之类的,这里我采用的是修改style方式
//1.首先在styles文件中添加启动时的默认图片
//2.然后在AndroidManifest文件中设置theme
//最后,如果你的启动页的背景和style中的几乎一样的话,还可以在启动页的activity中取消setContentView
九、Retrofit同时发送两次请求以及后台正常返回数据,客户端却回调失败onError closed信息
9.1 Retrofit同时发送两次请求
原因:
一般我们使用retrofit的时候,都会去使用拦截器。这时,如果多个拦截器里面都会调用chain.proceed()方法,则会同时发起多次网络请求,每调用一次chain.proceed()方法,都会发起一次请求。
解决方案:
可以将拦截器合成一个,保证只调用一次chain.proceed()方法。
9.2 后台正常返回数据,客户端却回调失败onError closed信息
原因:
如题目所示,虽然后台能够正常返回数据,但是客户端回调的却是onError,并且错误信息为closed。原因是在拦截器里面已经调用过response.body().string()方法,向上返回的response的responseBody已经被close掉了,所以错误信息显示的是closed。
解决方法:
在return的时候重新构建一个response。
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("X-APP-TYPE","android")
.build();
Response response = chain.proceed(request);
//打印请求的具体信息
String url = request.url().toString();
String params = requestBodyToString(request.body());
String responseString = response.body().string();//JsonHandleUtils.jsonHandle(response)
String time = DateUtils.getCurrentDateByFormat("yyyy-MM-dd HH:mm:ss");
String log =
"\n\n******请求时间*****:\n" + time +
"\n*****路径*****:\n" + url +
"\n*******参数******:\n" + params +
"\n*****报文******:\n" + responseString+"\n \n";
Log.d(TAG, log);
return response.newBuilder().body(ResponseBody.create(response.body().contentType(), responseString)).build();
}
参考链接:
Retrofit2的一些坑
十、Retrofit上传图片时Multipart注解和FormUrlEncoded注解冲突
问题:
当我们需要上传图片时,需要Multipart注解;当我们还有其他参数需要用POST方法传递时,需要FormUrlEncoded注解。当我们在同一个请求方法上同时加上这两个注解时,就会报冲突异常。
解决方案:
将需要传递的其他参数的Field换成Part即可
//错误写法
@FormUrlEncoded
@Multipart
@POST("api-user/user/uploadAvatar")
Observable uploadAvatar(
@Part MultipartBody.Part avatar,
@Field("userId") int userId);
//正确写法
@Multipart
@POST("api-user/user/uploadAvatar")
Observable uploadAvatar(
@Part MultipartBody.Part avatar,
@Part("userId") int userId);