Android Application Thread CPU GC Operatiing and OOM Question 0603-隨手筆記,operatiing0603-
在之前app寫完測試的時候,跑完整個老化階段包括資料收發都沒問題,鍵入 adb shell top -m 5 發現我的 app pid 佔用的
CPU是最多的,其實我想說寫一個app是不難,你又沒有全面的分析app的記憶體佔用?避免一些OOM之類的問題,和其他可
能帶來的一些偶發性問題,這些估計很多小夥伴都沒考慮,沒事,今天就給大夥說說這方面的東西,雖說不是什麼高難度的
知識點,但最重要的是養成這種習慣,才能在後續的開發中減少不必要的時間浪費,下面我就帶大家怎麼發現並且解決問
題,一步一步分析
首先看看 我們的app cpu 佔用情況:
我們可以看到 com.digissin.twelve 這個進程是一直排在第一位的,這個就是我們測試的進程,下面我帶小夥伴們怎麼發現問
題,並且及時糾正
首先我們要分析,為什麼CPU 佔用會那麼高?是不是在主線程或者子線程做了耗時操作,網路操作,new 的執行個體對象過多?
帶著這個疑問,我們看看DDMS並且分析下:
查看 com.digissin.twelve.RSUDPProtocol&PostBytesThread 134 行代碼:
死迴圈讀取狀態導致的,但又不能去掉這個死迴圈,因為app需要這個死迴圈來給服務端進行通訊,只要非意外情況,app是
一直和後台保持通訊的!當有資料傳過來,isPause 會被設成true,代碼流程就會走到if裡面,一旦發完一條資料報,
isPause false while 就用進入了空死迴圈,不幹任何事情,且頻率很快的迴圈執行,如果我們在這個死迴圈裡面調用sleep()
雖然能成功,但是很顯然它是與app需求背道而馳的,所以必須排除,因為一旦進入sleep() 線程就不幹活了,來自主線成的
協議分發的資料報發送就沒任何意義了!所以這個方法就不可取了
所以我很快想到了一個辦法,就是當isPause false 的時候,我們就不需要子線程工作,那很簡單,我只需要讓他休眠,一旦
有來自協議分發過來的資料報,我們就wakeup 讓子線程繼續工作,那就非 wait() 和 notify() 莫屬了 首先區分 Thread 和
Object 的 這兩個東西裡面的 wait() 和 notify() ,源碼分析太笼統了,我給大家舉例子分析
在Thread 裡直接調用這2兩個函數是不會起作用的,我們需要建立一個Object對象來管理子線程的暫停和繼續,意思就是說
子線程相當於一個普通員工,被new 出來的Object對象相當於一個管理者,員工要做什麼需要管理者來通知和告知,即使員
工知道自己下一步該幹什麼想幹什麼,都需要管理者的允許才行!員工也沒法自己獨立出來,就是不能自己做自己的事情,
否則整個管理員模式會亂套,所以我們必須建立Object對象來對子線程做這個暫停和繼續的控制著
所以我給這個內部類線程加 synchronized 欄位,並且添加執行個體化靜態方法,來建立這個Object(PostBytesThread)執行個體對象
別且給出暫停和繼續函數:
private static PostBytesThread mThreadInstance = null; public synchronized static PostBytesThread getThreadInstance() { if (mThreadInstance == null) { mThreadInstance = new PostBytesThread(); } return mThreadInstance; } public synchronized boolean isPause() {return isPause;}public synchronized void setPause(boolean isPause) {this.isPause = isPause;}public byte[] getPost_bytes() {return post_bytes;}public void setPost_bytes(byte[] post_bytes) {this.post_bytes = post_bytes;}public synchronized void onThreadPause(){try {Log.e(TAG, TAG+" onThreadPause() ----");this.wait();} catch (InterruptedException e) {Log.i(TAG, e.toString());}}public synchronized void onThreadResume(){Log.e(TAG, TAG+" onThreadResume() ----");this.notify();} @Override public void run() { if(udpSocket == null){ Log.i(TAG, TAG+" udpSocket is null"); return; } while(true){ Log.i(TAG, TAG+" isPause() state:"+isPause()); if(isPause()){ try { sendPacket.setData(getPost_bytes()); sendPacket.setLength(getPost_bytes().length); sendPacket.setAddress(serverAddress); sendPacket.setPort(DEFAULT_POTR); udpSocket.send(sendPacket); Thread.sleep(1000); setPause(false); } catch (InterruptedException e) { Log.i(TAG, "Exception:"+e.toString()); } catch (IOException e) { Log.i(TAG, "Exception:"+e.toString()); } }else{ onThreadPause(); }} } }
調用方式,回調介面收到來自主線程的協議訊息資料包分發,並開始工作,當然只是為了方便大家觀看,其實start()方法不用發在這裡,因為這個同步對象只有在子線程消亡才會被回收,所以相當於每次都多判斷了一次這個同步對象的執行個體情況了
public void setPostBytesData(byte[] data){ PostBytesThread.getThreadInstance().start(); PostBytesThread.getThreadInstance().onThreadResume(); PostBytesThread.getThreadInstance().setPause(true); PostBytesThread.getThreadInstance().setPost_bytes(data); boolean isPause = PostBytesThread.getThreadInstance().isPause(); Log.d("PostBytesThread", "PostBytesThread isPause() state:"+isPause); }
處理完這段代碼後我們繼續查看 cpu的佔用情況:
可以看到com.digissin.twelve的CPU佔用大幅降低了,從而達到了我們的目的,在解決這個問題的同時,我也給大家說一個
常犯的錯誤,並且以代碼和注釋的形式給大家看清楚
建立不必要的新執行個體:
在一些進度條更新或者上傳下載資料等情況,我們通常需要對UI進行跟新之類的,這就涉及子線程跟Handler的互動,需要
我們不停地向Handler發送Message 對象,這時候就易犯這個錯誤,如下:
@Overridepublic void run() {while(true){try {SettingLocationTime();} catch (InterruptedException e) {e.printStackTrace();}}}private void SettingLocationTime() throws InterruptedException{if(handler!=null){SendMessage(post_data);time = setting_time>0?setting_time:default_time;//Log.i(TAG, TAG+" SettingLocationTime() time:"+time);Thread.sleep(time*1000);}}/** * 這個函數會在run while(true)裡面一直跑 * Message\Bundle會被不停的建立新執行個體對象 * 所以這是個極低的錯誤!也是致命的! * */private void SendMessage(byte[]data){byte[]_data=ByteParseBeanTools.PostProtocolByte(ByteProtocolSessionType.LOCATION_STATE_SEND, data);Message msg = new Message(); // 不必要的 Message 新執行個體對象msg.what=MainSessionUtil.SEND_POST_BYETS_DATA;Bundle bundle = new Bundle(); // 不必要的 Bundle 新執行個體對象bundle.putByteArray(MainSessionUtil.BYTES_DATA_KEY, _data);msg.setData(bundle);handler.sendMessage(msg);}
解決方案:
@Overridepublic void run() {while(true){try {SettingLocationTime();} catch (InterruptedException e) {e.printStackTrace();}}}private void SettingLocationTime() throws InterruptedException{if(handler!=null){SendMessage(post_data);time = setting_time>0?setting_time:default_time;//Log.i(TAG, TAG+" SettingLocationTime() time:"+time);Thread.sleep(time*1000);}}/** * 可以把Bundle放在class被載入的地方,執行個體化這個對象 * 裝載完一次資料之後,下次調用之前執行clear()函數即可,此時的bundle對象就相當於一個鐵碗 * 每次裝不同的水而已,就避免了每次開闢新的記憶體空間來存放Bundle對象 * Message 對象就更簡單了,因為我這類回調了一個Handler對象過來,我們可以直接 * 調用Handler對象的obtainMessage()函數,這個函數當Handler被建立時,不管你用不用,它都在那裡 * 隨Handler消亡而消亡,不需要執行個體化,不需要建立,可以直接取出來用,這又避免了每次開闢新的記憶體空間 * 來裝載Message對象,obtainMessage() 函數 來自 MessagePool * **/private void SendMessage(byte[]data){bundle.clear();// 倒掉碗裡的老水(清空之前的緩衝),裝新來的水(填充來自回呼函數的新資料)byte[]_data=ByteParseBeanTools.PostProtocolByte(ByteProtocolSessionType.LOCATION_STATE_SEND, data);Message msg = handler.obtainMessage(); // 來自 MessagePoolmsg.what=MainSessionUtil.SEND_POST_BYETS_DATA;bundle.putByteArray(MainSessionUtil.BYTES_DATA_KEY, _data);// 裝新的水(填充新的資料來源)msg.setData(bundle);handler.sendMessage(msg);}
這樣CPU佔用問題就能大幅降低,從而問題也能得到解決!