11月踩坑總結(jié)

最近完成了一個(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ò)的圖:


屬性說(shuō)明

2、配置菜單屬性

菜單屬性說(shuō)明

上圖中仇箱,右上角菜單是黑色的县恕,想將其設(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)雨兼程俊柔!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末筹麸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雏婶,更是在濱河造成了極大的恐慌物赶,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留晚,死亡現(xiàn)場(chǎng)離奇詭異酵紫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)错维,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門憨闰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人需五,你說(shuō)我怎么就攤上這事鹉动。” “怎么了宏邮?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵泽示,是天一觀的道長(zhǎng)缸血。 經(jīng)常有香客問(wèn)我,道長(zhǎng)械筛,這世上最難降的妖魔是什么捎泻? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮埋哟,結(jié)果婚禮上笆豁,老公的妹妹穿的比我還像新娘。我一直安慰自己赤赊,他們只是感情好闯狱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抛计,像睡著了一般哄孤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吹截,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天瘦陈,我揣著相機(jī)與錄音,去河邊找鬼波俄。 笑死晨逝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的懦铺。 我是一名探鬼主播捉貌,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼阀趴!你這毒婦竟也來(lái)了昏翰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刘急,失蹤者是張志新(化名)和其女友劉穎棚菊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叔汁,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡统求,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了据块。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片码邻。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖另假,靈堂內(nèi)的尸體忽然破棺而出像屋,到底是詐尸還是另有隱情,我是刑警寧澤边篮,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布己莺,位于F島的核電站奏甫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凌受。R本人自食惡果不足惜逆害,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一拌禾、第九天 我趴在偏房一處隱蔽的房頂上張望寥茫。 院中可真熱鬧沮明,春花似錦、人聲如沸誊册。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)解虱。三九已至攘须,卻和暖如春漆撞,著一層夾襖步出監(jiān)牢的瞬間殴泰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工浮驳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悍汛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓至会,卻偏偏與公主長(zhǎng)得像离咐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奉件,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,190評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理宵蛀,服務(wù)發(fā)現(xiàn),斷路器县貌,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)术陶、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,109評(píng)論 4 62
  • 轉(zhuǎn)眼之間,又一個(gè)3年多跟自己說(shuō)了聲拜拜摆碉!在我的人生軌跡里塘匣,也許生命的版本就是那種每天穿梭于工廠與家的兩點(diǎn)一線!迄今...
    凱強(qiáng)工作室1閱讀 138評(píng)論 2 0
  • 從事語(yǔ)言服務(wù)方面的業(yè)務(wù)有將近一年的時(shí)間了巷帝,自己也一直在思考語(yǔ)言服務(wù)的價(jià)值究竟應(yīng)該在哪里忌卤?再加上今年4月份接受通用應(yīng)...
    吾往閱讀 509評(píng)論 0 0