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;
}
-
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數據爲空;
-
視頻播放全屏底部白條問題 : 自己調整佈局 以及設置 正確的參數 :
surface_view.getHolder().setFixedSize(mSurfaceViewWidth, mSurfaceViewHeight); -
代碼設置TextView的字體大小 :記住默認是以SP爲單位的,所以不用再轉px了。
-
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();
}
}
});
-
postDelayed 方法中運行的Runable是主線程調了Runnable的run方法而已,細節忘了,坑。。。。
postDelayed(new Runnable() {
@Override
public void run() {
}
},1000); -
更新umeng : 從4.6升級到5.0版 真的如官方所說,變化很大,很多api沒有了 更新謹慎之
-
設置 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程序,修改了所有使用這個控件的屬性方法,從源頭解決方法
-
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>
-
Android 開發的時候在Application開啓 嚴格模式,會查找到很多問題代碼:
/**
* 嚴格模式開啓
*/
private void setStrictMode() { if (LogUtil.isDebug && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
StrictMode.enableDefaults();
}
}
-
butterknife 插件使用: 鼠標點到R.layout.activity_main佈局 ,再右鍵Generate–》ButterKnife 選項
-
MuritaleDex :這個是65536的問題,APP項目功能越來越多,引用越來越多第三方的Jar包的時候,就有很大的概率觸發這個問題。Android5.0以上的系統,不需要擔心這個問題。
compile ‘com.android.support:multidex:1.0.1’//引用multidex庫
-
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,解決了更新用戶信息的問題。