1. 程式人生 > >Android 專案中遇到的坑,特此記錄

Android 專案中遇到的坑,特此記錄

WebView的記憶體洩露。

當你要用webview的時候,記得最好 另外單獨開一個程序 去使用webview 並且當這個 程序結束時,請手動呼叫System.exit(0)。這是目前對於webview 記憶體洩露 最好的解決方案。使用此方法 所有因為webview引發的 資源無法釋放等問題 全部可以解決。

getSettings().setBuiltInZoomControls(true) 引發的crush。

這個方法呼叫以後 如果你觸控式螢幕幕 彈出那個提示框還沒消失的時候 你如果activity結束了 就會報錯了。3.0以上 4.4以下很多手機會出現這種情況所以為了規避他,我們通常是在activity的onDestroy方法裡手動的將webiew設定成 setVisibility(View.GONE)

3.onPageFinished 函式到底有用沒有?

多數開發者都是參考的http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android 這個上面的高票答案。

但其實根據我自己觀察,這個函式並沒有什麼卵用,有的時候是提前結束,有的時候就遲遲無法結束,你信這個函式 還不如信上帝,甚至於onProgressChanged這個函式

都比onPageFinished  要準一些。如果你的產品經理堅持你一定要實現這種功能的話,我建議你 提早結束他,否則卡在那使用者遲遲動不了 這種體驗不好。

有空的同學可以跟一下原始碼,onPageFinished  在不同的核心裡 呼叫的時機都不一樣。說實話 我也很醉。。。這個問題 有完美解決方案的 請知會我一下。。。

4.後臺無法釋放js 導致耗電。

這個可能很少有人知道,我也是被投訴過 才瞭解,在有的手機裡,你如果webview載入的html裡 有一些js 一直在執行比如動畫之類的東西,如果此刻webview 掛在了後臺

這些資源是不會被釋放 使用者也無法感知。。。導致一直佔有cpu 耗電特別快,所以大家記住了,如果遇到這種情況 請在onstop和onresume裡分別把setJavaScriptEnabled();

給設定成false和true。

5.如果實在不想用開額外程序的方式解決webview 記憶體洩露的問題,那麼下面的方法很大程度上可以避免這種情況

複製程式碼
 1     public void releaseAllWebViewCallback() {
 2         if (android.os.Build.VERSION.SDK_INT < 16) {
 3             try {
 4                 Field field = WebView.class.getDeclaredField("mWebViewCore");
 5                 field = field.getType().getDeclaredField("mBrowserFrame");
 6                 field = field.getType().getDeclaredField("sConfigCallback");
 7                 field.setAccessible(true);
 8                 field.set(null, null);
 9             } catch (NoSuchFieldException e) {
10                 if (BuildConfig.DEBUG) {
11                     e.printStackTrace();
12                 }
13             } catch (IllegalAccessException e) {
14                 if (BuildConfig.DEBUG) {
15                     e.printStackTrace();
16                 }
17             }
18         } else {
19             try {
20                 Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
21                 if (sConfigCallback != null) {
22                     sConfigCallback.setAccessible(true);
23                     sConfigCallback.set(null, null);
24                 }
25             } catch (NoSuchFieldException e) {
26                 if (BuildConfig.DEBUG) {
27                     e.printStackTrace();
28                 }
29             } catch (ClassNotFoundException e) {
30                 if (BuildConfig.DEBUG) {
31                     e.printStackTrace();
32                 }
33             } catch (IllegalAccessException e) {
34                 if (BuildConfig.DEBUG) {
35                     e.printStackTrace();
36                 }
37             }
38         }
39     }
複製程式碼

在webview的 destroy方法裡 呼叫這個方法就行了。

6.另外很多人 不知道webview 實際上有自己一套完整的cookie機制的,利用好這個 可以大大增加對客戶端的訪問速度。

實際上cookie就是存放在這個表裡的。

很多人都想要一個效果:網頁更新cookie 設定完cookie以後 不重新整理頁面即可生效。這個在2.3以下和2.3以上要實現的方法不太一樣,所以要做一次相容

複製程式碼
 1   
 4     public void updateCookies(String url, String value) {
 5         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { // 2.3及以下
 6             CookieSyncManager.createInstance(getContext().getApplicationContext());
 7         }
 8         CookieManager cookieManager = CookieManager.getInstance();
 9         cookieManager.setAcceptCookie(true);
10         cookieManager.setCookie(url, value);
11         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
12             CookieSyncManager.getInstance().sync();
13         }
14     }
複製程式碼

1.載入長圖,如新浪微博裡面的長微博,這種圖,特別大,如果專案中使用的圖片縮放控制元件,要注意所使用的第三方載入(如:Glide,Picasso),因其內部已經做過壓縮處理,導致長圖特別模糊,單獨某張卡片的載入 可不使用。自己單獨實現壓縮方法。

/**
 * 圖片壓縮工具類
 *
 * @author 
 */
public class BitmapCompressUtils {
    public static final String CONTENT = "content";
    public static final String FILE = "file";

    /**
     * 圖片壓縮引數
     *
     * @author Administrator
     */
    public static class CompressOptions {
        public static final int DEFAULT_WIDTH = 400;
        public static final int DEFAULT_HEIGHT = 800;

        public int maxWidth = DEFAULT_WIDTH;
        public int maxHeight = DEFAULT_HEIGHT;
        /**
         * 壓縮後圖片儲存的檔案
         */
        public File destFile;
        /**
         * 圖片壓縮格式,預設為jpg格式
         */
        public CompressFormat imgFormat = CompressFormat.JPEG;

        /**
         * 圖片壓縮比例 預設為30
         */
        public int quality = 30;

        public Uri uri;
        public String path;
    }

    public Bitmap compressFromUri(Context context,
                                  CompressOptions compressOptions) {

        // uri指向的檔案路徑
        // String filePath = getFilePath(context, compressOptions.uri);
        String filePath = compressOptions.path;
        if (null == filePath) {
            return null;
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        Bitmap temp = BitmapFactory.decodeFile(filePath, options);

        int actualWidth = options.outWidth;
        int actualHeight = options.outHeight;

        int desiredWidth = getResizedDimension(compressOptions.maxWidth,
                compressOptions.maxHeight, actualWidth, actualHeight);
        int desiredHeight = getResizedDimension(compressOptions.maxHeight,
                compressOptions.maxWidth, actualHeight, actualWidth);

        options.inJustDecodeBounds = false;
        options.inSampleSize = findBestSampleSize(actualWidth, actualHeight,
                desiredWidth, desiredHeight);

        Bitmap bitmap = null;

        Bitmap destBitmap = BitmapFactory.decodeFile(filePath, options);

        // If necessary, scale down to the maximal acceptable size.
      /*  if (destBitmap.getWidth() > desiredWidth
                || destBitmap.getHeight() > desiredHeight) {
            bitmap = Bitmap.createScaledBitmap(destBitmap, desiredWidth,
                    desiredHeight, true);
            destBitmap.recycle();
        } else {
            bitmap = destBitmap;
        }*/
        bitmap = destBitmap;
        // compress file if need
        if (null != compressOptions.destFile) {
            compressFile(compressOptions, bitmap);
        }

        return bitmap;
    }

    /**
     * compress file from bitmap with compressOptions
     *
     * @param compressOptions
     * @param bitmap
     */
    private void compressFile(CompressOptions compressOptions, Bitmap bitmap) {
        OutputStream stream = null;
        try {
            stream = new FileOutputStream(compressOptions.destFile);
        } catch (FileNotFoundException e) {
            Log.e("ImageCompress", e.getMessage());
        }

        bitmap.compress(compressOptions.imgFormat, compressOptions.quality,
                stream);
    }

    private static int findBestSampleSize(int actualWidth, int actualHeight,
                                          int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 1.5) <= ratio) {
            n *= 1.5;
        }

        return (int) n;
    }

    private static int getResizedDimension(int maxPrimary, int maxSecondary,
                                           int actualPrimary, int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling
        // ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * 獲取檔案的路徑
     *
     * @param
     * @return
     */
    private String getFilePath(Context context, Uri uri) {

        String filePath = null;

        if (CONTENT.equalsIgnoreCase(uri.getScheme())) {

            Cursor cursor = context.getContentResolver().query(uri,
                    new String[]{Images.Media.DATA}, null, null, null);

            if (null == cursor) {
                return null;
            }

            try {
                if (cursor.moveToNext()) {
                    filePath = cursor.getString(cursor
                            .getColumnIndex(Images.Media.DATA));
                }
            } finally {
                cursor.close();
            }
        }

        // 從檔案中選擇
        if (FILE.equalsIgnoreCase(uri.getScheme())) {
            filePath = uri.getPath();
        }

        return filePath;
    }
    // 流轉File
    public static File Stream2File(InputStream stream, String Filename) {
        File file = null;
        FileOutputStream fileOutputStream = null;
        try {
            file = new File(Constant.DOWNLOAD_IMAGE_PATH+Filename);
            fileOutputStream = new FileOutputStream(file);
            int length;
            byte[] buf = new byte[1024];
            while((length =stream.read(buf,0,buf.length)) !=-1){
                fileOutputStream.write(buf,0,length);
                fileOutputStream.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileOutputStream !=null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(stream !=null){
                try {
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file;
    }
}
載入圖片如下:
<pre name="code" class="java">path為圖片url
</pre><p></p><pre>
                int subPostion = path.lastIndexOf("/");
                final String fileName = path.substring(subPostion,path.length());
                
                File file = new File(Constant.DOWNLOAD_IMAGE_PATH+fileName);
                if(file.exists()){
                    BitmapCompressUtils compress = new BitmapCompressUtils();
                    BitmapCompressUtils.CompressOptions options = new BitmapCompressUtils.CompressOptions();
                    options.path = file.getAbsolutePath();
                    options.maxWidth=Constant.PHONE_WHITH;
                    options.maxHeight=Constant.PHONE_HEIGHT;
                    Bitmap map = compress.compressFromUri(mContext, options);
                    view.setImageBitmap(map);
                }else {
                    AsyncThread.getInstance().start(new AsyncThread.OnListener() {
                        @Override
                        public Object doInBackground() {
                            // httpURLConnection方式解析
                            Bitmap map = null;
                            try {
                                URL url = new URL(path);
                                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                                conn.setDoInput(true);
                                conn.connect();
                                InputStream is = conn.getInputStream();
                                File file = BitmapCompressUtils.Stream2File(is, fileName);
                                BitmapCompressUtils compress = new BitmapCompressUtils();
                                BitmapCompressUtils.CompressOptions options = new BitmapCompressUtils.CompressOptions();
                                options.path = file.getAbsolutePath();
                                options.maxWidth = Constant.PHONE_WHITH;
                                options.maxHeight = Constant.PHONE_HEIGHT;
                                map = compress.compressFromUri(mContext, options);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return map;
                        }

                        @Override
                        public void doFinish(Object object) {
                            Bitmap bitmap = (Bitmap) object;
                            //scaleView.setBackground(bd);
                            Log.e("bitmap", bitmap.getHeight() + "");
                            view.setImageBitmap(bitmap);
                            // ll_root.addView(imageViewTouch);
                        }

                        @Override
                        public void error(Exception e) {
                            e.printStackTrace();
                        }
                    });
                }

2.然後就是資料庫增加欄位要考慮周全,版本號,onUpgrade要考慮不同版本升級到最新版本,若新增欄位忘記升級版本號(低階錯
誤!該打)要在升級方法中判斷。


方法如下:

    /**
     * 檢查表中某列是否存在
     *
     * @param db
     * @param tableName  表名
     * @param columnName 列名
     * @return
     */
    private boolean checkColumnExists(SQLiteDatabase db, String tableName
            , String columnName) {
        boolean result = false;
        Cursor cursor = null;
        try {
            //查詢一行
            cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0"
                    , null);
            result = cursor != null && cursor.getColumnIndex(columnName) != -1;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != cursor && !cursor.isClosed()) {
                cursor.close();
            }
        }

        return result;
    }
  1. Activity之間跳轉的生命週期問題 : 
    背景 :有兩個Activity A和B,A跳轉到B,全域性靜態屬性BitmapUtil.drr記錄了檔案的路徑資料;A跳轉到B時,A在onDestroy裡清空drr資料,請問B在onCreate方法和onResume方法裡讀取到的drr資料是不是為空?

    測試結果:A跳轉到B, A在onDestroy裡清空了BitmapUtil.drr資料,導致在B的onCreate方法讀取drr資料不為空,但onResume方法中讀取的drr資料為空;

  2. 視訊播放全屏底部白條問題 : 自己調整佈局 以及設定 正確的引數 : 
    surface_view.getHolder().setFixedSize(mSurfaceViewWidth, mSurfaceViewHeight);

  3. 程式碼設定TextView的字型大小 :記住預設是以SP為單位的,所以不用再轉px了。

  4. setOnScrollListener 滑動監聽: ListView第一次初始化就會呼叫onScroll方法,坑啊

 //滾動監聽
  pull_list_grid.setOnScrollListener(new AbsListView.OnScrollListener() {   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {

   }   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {//                Logger.e("firstVisibleItem::" + firstVisibleItem + " visibleItemCount :" +visibleItemCount +"  totalItemCount :"+totalItemCount);
    if (!TextUtils.isEmpty(keyword)&&!noMoreData && totalItemCount - firstVisibleItem < CommConfig.LetterLoadMore_SIZE && !httpIng) {
     getMorePage();
    }
   }
  });
  1. postDelayed 方法中執行的Runable是主執行緒調了Runnable的run方法而已,細節忘了,坑。。。。

    postDelayed(new Runnable() { 
    @Override 
    public void run() { 

    },1000);

  2. 更新umeng : 從4.6升級到5.0版 真的如官方所說,變化很大,很多api沒有了 更新謹慎之

  3. 設定 android:allowBackup=”false” 這個屬性存在bug,模式是true,在正式釋出app的時候設定為false,但一般專案引用多個第三方庫的時候,會存在坑 
    多個衝突,導致打包APP失敗,檢視日誌也找到了google給出的建議,如下:

    Suggestion: add ‘tools:replace=”android:allowBackup”’ to element at AndroidManifest.xml:89:5-1052:19 to override.

    所以新增 :

tools:replace="android:allowBackup"

8.使用優測,發現一些安全漏洞和一些bug 但TM按照給出的修改建議,修改bug後,再測試還是有相同的漏洞,表示很坑啊。。。。

9.TextView 同時顯示錶情和文字, 可能存在表情被遮擋部分或者文字表情不居中顯示bug, 操蛋,這個除錯了好久,嘗試過設定表情大小,文字大小,發現都不能根本解決問題,除錯好久, 
最終發現設定TextView的高度為wrap_content是不行的,要設定為相應的高度值 ,比如20dp 就OK了

10.自定義屬性和support:appcompat-v7:22.2.0包屬性衝突 : 根本的解決方法都是更改自定義控制元件的屬性,但因為我的專案中大量使用了這個控制元件,以及專案緊張,

所以找了一個暫時的解決方法

    <attr name="title" format="string" />
    <attr name="titleTextSize" format="dimension" />
    <attr name="titleTextColor" format="color" />
    <attr name="title" />
    <attr name="titleTextSize"/>
    <attr name="titleTextColor"/>

最後,我抽空寫了一個java程式,修改了所有使用這個控制元件的屬性方法,從源頭解決方法

  1. APP啟動顯示預設的啟動頁面(跟微信類似) : 關鍵程式碼 android:theme=”@style/AppSplash”

<!--啟動頁面-->
        <activity            android:name=".activity.login.SplashActivity_"
            android:theme="@style/AppSplash">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


            <!--啟動頁面主題   可以自己定製 -->
        <style name="AppSplash" parent="android:Theme">
            <item name="android:windowBackground">@drawable/splash_bg</item>
            <item name="android:windowNoTitle">true</item>
            <item name="android:windowFullscreen">true</item>        </style>
  1. Android 開發的時候在Application開啟 嚴格模式,會查詢到很多問題程式碼:

/**
  * 嚴格模式開啟
  */
 private void setStrictMode() {  if (LogUtil.isDebug && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
   StrictMode.enableDefaults();
      }
 }
  1. butterknife 外掛使用: 滑鼠點到R.layout.activity_main佈局 ,再右鍵Generate–》ButterKnife 選項

  2. MuritaleDex :這個是65536的問題,APP專案功能越來越多,引用越來越多第三方的Jar包的時候,就有很大的概率觸發這個問題。Android5.0以上的系統,不需要擔心這個問題。

    compile ‘com.android.support:multidex:1.0.1’//引用multidex庫

  3. leak canary 記憶體洩漏檢測工具 :下面這段程式碼這能放在專案APP的build檔案中,而不能放在任何第三放的aar的build檔案中。 
    真心坑!

  //leak canary  記憶體洩漏檢測工具
    //https://github.com/square/leakcanary
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'

LeakCanary工作原理 
•RefWatcher.watch()建立一個KeyedWeakReference到監控的物件。 
•接下來,在後臺執行緒中檢測這個引用是否被清除,如果沒有將會觸發GC。 
•如果引用仍然沒有清除,將heap記憶體dump到一個.hprof的檔案存放到手機系統裡。 
•HeapAnalyzerService在另外一個獨立的程序中啟動,使用HeapAnalyzer解析heap記憶體通過HAHA這個專案 
•HeapAnalyzer計算出到GC ROOTs的最短強引用路徑決定是否發生Leak,然後建立導致洩漏的引用鏈。 結果被回傳到應用程式程序的DisplayLeakService中,然後顯示一個洩漏的通知。

16 . Android高階開發之效能優化典範 值得一看 ,開發規範很重要

17 . LinearLayout中設定android:orientation=”horizontal” ,它的高度以第一個view的高度為準,導致高度不對,解決方法: 
在第一個View的外層新增一個LinearLayout ,設定高度為 android:layout_height=”match_parent”

18.multidex引發的後遺症, 當修改MAinActivity的FindFragment為InfoFragment時,會包Dex包中不存在InfoFragment類,其實是因為手機上的dex是舊的dex包,導致沒有更新所致, 
解決方法是刪除掉手機上的APP,然後Clean一下AS工具,重新安裝App即可

19.複製保留舊的XML佈局檔案,時間久了發現後面無法刪除,查了好久都沒發現什麼問題導致,今天終於一點點刪除XML佈局檔案的內容,刪除到最後,發現有幾個不能刪除,一刪除就編譯錯誤, 
檢視最新的XML佈局程式碼中,沒有這幾個內容,再看Activity中程式碼,發現在點選事件中,還留存著這幾個Id,我擦,原來就是這個問題。

20 . 以後不要保留舊的XML檔案 ,容易引發亂七八糟的bug,坑 
21 . 融雲重新整理使用者資訊無效問題: 
不能直接在你實現的類裡非同步請求更新userinfo,你看日誌的會發現,其實融雲根本沒呼叫戶介面請求,暫時沒有去查原因,我的解決方法是通過EventBus訊息傳到MainActivity中再請求userinfo,然後更新userinfo,解決了更新使用者資訊的問題。