Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Application Thread CPU GC Operatiing and OOM Question 0603-隨手筆記

Android Application Thread CPU GC Operatiing and OOM Question 0603-隨手筆記

編輯:關於Android編程

在之前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 對象,這時候就易犯這個錯誤,如下:

 

	@Override
	public 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);
	}

解決方案:

 

 

	@Override
	public 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();  // 來自 MessagePool
		msg.what=MainSessionUtil.SEND_POST_BYETS_DATA;
		bundle.putByteArray(MainSessionUtil.BYTES_DATA_KEY, _data);// 裝新的水(填充新的數據源)
		msg.setData(bundle);
		handler.sendMessage(msg);
	}

這樣CPU占用問題就能大幅降低,從而問題也能得到解決!

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved