最近完成了一個(gè)新項(xiàng)目的開發(fā)蝉娜, 期間一把辛酸淚丙者,在此記錄下一些常見的坑花墩,供自己和以后踩坑的小伙伴參考悬秉!
一、toolbar 相關(guān)配置
1冰蘑、配置toolbar的背景顏色和狀態(tài)欄顏色
在style文件中新建主題進(jìn)行配置和泌,比如我的:
<style name="IMTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
當(dāng)然,別忘了在Application或者需要的Activity節(jié)點(diǎn)上設(shè)置這個(gè)主題祠肥。
關(guān)于這些屬性武氓,找到了一張不錯(cuò)的圖:
2、配置菜單屬性
上圖中仇箱,右上角菜單是黑色的县恕,想將其設(shè)置為白色,方法同上:
<style name="ToolbarTheme" parent="@style/ThemeOverlay.AppCompat.ActionBar">
<item name="actionMenuTextColor">@color/white</item> <!-- 敲定顏色-->
<item name="android:textSize">18sp</item> <!-- 搞掂字體大小-->
<!-- navigation icon color -->
<item name="colorControlNormal">@color/white</item>
<!-- color of the menu overflow icon -->
<item name="android:textColorSecondary">@color/white</item>
</style>
將主題配置到toolbar上:
app:theme="@style/ToolbarTheme"
3剂桥、配置彈出菜單屬性
點(diǎn)擊上圖的右上角忠烛,會(huì)彈出菜單列表,列表的字體顏色和背景也是可以配置的权逗,其關(guān)鍵在于繼承這個(gè)主題:
<style name="Base.Widget.AppCompat.ActionButton.Overflow" parent="RtlUnderlay.Widget.AppCompat.ActionButton.Overflow">
<item name="android:src">@drawable/abc_ic_menu_moreoverflow_mtrl_alpha</item>
<item name="android:background">?attr/actionBarItemBackground</item>
<item name="android:contentDescription">@string/abc_action_menu_overflow_description</item>
<item name="android:minWidth">@dimen/abc_action_button_min_width_overflow_material</item>
<item name="android:minHeight">@dimen/abc_action_button_min_height_material</item>
</style>
然后在toolbar上配置:
app:popupTheme="@style/YourStyle"
二美尸、相冊(cè)圖片的路徑
在4.4之后,打開相冊(cè)斟薇,選擇圖片之后得到的并不是圖片的真實(shí)路徑火惊,我想這一點(diǎn)幾乎所有開發(fā)者都清楚,但是知道和解決是兩碼事奔垦。我踩的坑在于屹耐,該項(xiàng)目是一個(gè)混合開發(fā)的模式,打開相冊(cè)椿猎,選擇圖片惶岭,得到路徑這一系列操作都是用的cordova插件,然后前端直接就返回給我一個(gè)路徑了犯眠。在調(diào)試階段按灶,我的5.1版本的手機(jī),返回的也都是真實(shí)的圖片路徑筐咧,沒(méi)有任何問(wèn)題鸯旁,當(dāng)時(shí)心里還不由得贊了一下cordova噪矛,路徑問(wèn)題都已經(jīng)處理好了。等到打了正式包铺罢,進(jìn)行測(cè)試的時(shí)候悲劇了艇挨,我再一看,握草韭赘,怎么路徑變了K醣酢!泉瞻!至今我也不知道cordova返回的路徑為什么變化了脉漏,但是,解決方案是很明顯的袖牙,需要我們自己轉(zhuǎn)化嘛侧巨,貼上StackOverflow上大神的代碼:
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
三、retrofit文件上傳
選了圖片鞭达,接下來(lái)肯定就是上傳了刃泡。我的網(wǎng)絡(luò)框架用的retrofit,網(wǎng)上也有許多大神講解其用法碉怔。這里就直接討論其文件上傳的用法烘贴,首先來(lái)看我定義的方法:
@Multipart
@POST("call")
Call<UploadFileInfo> upLoadPhoto(@PartMap Map<String, RequestBody> map,
@Part("files\"; filename=\"photo.JPEG")
@POST("call")
@Multipart
Call<UploadFileInfo> upLoadFile(@PartMap Map<String, RequestBody> map
, @Part MultipartBody.Part file);
@POST("call")
@Multipart
Call<UploadFileInfo> upLoadFiles(@PartMap Map<String, RequestBody> partMap);
很明顯,前兩種是單文件上傳撮胧,最后是多文件上傳桨踪。
而第一種對(duì)文件進(jìn)行了硬編碼,顯然是不可取的芹啥,那么科學(xué)的方式自然是后兩種锻离。直接上多文件的代碼:
private void uploadFiles(List<String> listUrl) {
Map<String, RequestBody> files = new HashMap<>();
MediaType imageType = MediaType.parse("image/*");
MediaType textType = MediaType.parse("text/plain");
for (String url : listUrl) {
File file = new File(url);
RequestBody fileBody = RequestBody.create(imageType, file);
String fileName = file.getName();
files.put("files\"; filename=\"" + fileName,fileBody);
}
RequestBody textParam = RequestBody.create(textType,"textParam");
files.put("textParam",textParam);
Retrofit retrofit = AppClient.getRetrofit(Constants.BASE_URL_YIWEN);
ApiStores apiStores = retrofit.create(ApiStores.class);
Call<UploadFileInfo> call = apiStores.upLoadFiles(files);
call.enqueue(new Callback<UploadFileInfo>() {
@Override
public void onResponse(Call<UploadFileInfo> call, Response<UploadFileInfo> response) {
}
@Override
public void onFailure(Call<UploadFileInfo> call, Throwable t) {
}
});
}
四、圖片壓縮
上傳的網(wǎng)絡(luò)請(qǐng)求本身沒(méi)有問(wèn)題了墓怀,但是有時(shí)會(huì)出現(xiàn)圖片過(guò)大導(dǎo)致上傳失敗的現(xiàn)象汽纠,于是,圖片壓縮就必不可少了傀履。這里推薦寫得挺好的文章:
Android圖片壓縮(質(zhì)量壓縮和尺寸壓縮)
android圖片壓縮總結(jié)
我用到了其中2個(gè)方法:
public static Bitmap compressImageFromFile(String srcPath){
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只讀邊,不讀內(nèi)容
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = 800f;//
float ww = 480f;//
int be = 1;
if (w > h && w > ww) {
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//設(shè)置采樣率
newOpts.inPreferredConfig = Bitmap.Config.ARGB_8888;//該模式是默認(rèn)的,可不設(shè)
newOpts.inPurgeable = true;// 同時(shí)設(shè)置才會(huì)有效
newOpts.inInputShareable = true;//虱朵。當(dāng)系統(tǒng)內(nèi)存不夠時(shí)候圖片自動(dòng)被回收
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
// return compressBmpFromBmp(bitmap);//原來(lái)的方法調(diào)用了這個(gè)方法企圖進(jìn)行二次壓縮
//其實(shí)是無(wú)效的,大家盡管嘗試
return bitmap;
}
/**
* Compress by quality, and generate image to the path specified
*
* @param image
* @param outPath
* @param maxSize target will be compressed to be smaller than this size.(kb)
* @throws IOException
*/
public static void compressAndGenImage(Bitmap image, String outPath, int maxSize) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
// scale
int options = 100;
// Store the bitmap into output stream(no compress)
image.compress(Bitmap.CompressFormat.JPEG, options, os);
// Compress by loop
while ( os.toByteArray().length / 1024 > maxSize) {
// Clean up os
os.reset();
// interval 10
options -= 10;
image.compress(Bitmap.CompressFormat.JPEG, options, os);
}
// Generate compressed image file
FileOutputStream fos = new FileOutputStream(outPath);
fos.write(os.toByteArray());
fos.flush();
fos.close();
}
最近有發(fā)現(xiàn)了一個(gè)可能是最接近微信朋友圈的圖片壓縮算法
五、打包
build.gradle腳本配置好了簽名文件钓账,也切換到了release模式碴犬,打出來(lái)的包還可能不能覆蓋老版本的包嗎?
在此之前梆暮,我覺(jué)得不會(huì)吧服协??@泊狻偿荷!
不過(guò)窘游,這次遇見了,其實(shí)原因也簡(jiǎn)單跳纳,但是在不知道之前忍饰,這個(gè)坑卻難填。首先棒旗,我拿到的源碼里面build.gradle中targetSdkVersion是22喘批,那么我就理所當(dāng)然的就認(rèn)為上個(gè)版本也是撩荣,也就這樣打包了铣揉。經(jīng)過(guò)幾番折騰才發(fā)現(xiàn),上個(gè)版本的包targetSdkVersion居然是23餐曹,這也就難怪了逛拱!
這里就推薦一個(gè)查看apk基本信息的命令,避免遇到此類坑:
aapt dump badging <file_path.apk>
效果如下:
注意使用此命令需要配置環(huán)境變量台猴,或者cd 至Android SDK的build-tools目錄下進(jìn)行朽合。
關(guān)于此命令更詳細(xì)的講解
打包提速
至此,總結(jié)就差不多了饱狂,通過(guò)這段時(shí)間的項(xiàng)目開發(fā)曹步,認(rèn)識(shí)到了自己某些方面的不足,也見識(shí)到了隔壁部門大牛的更加科學(xué)休讳、全面的分析和解決問(wèn)題的方法讲婚。
任重道遠(yuǎn),風(fēng)雨兼程俊柔!