Android系統CPU使用率獲取(附java程式碼)
若想直接看有效方法,請閱讀方法二,第一條。
最近因為一個需求,需要記錄下當前的CPU使用率,在翻遍了API後,發現系統並沒有給予一個方法,能夠簡單的獲取相關CPU資訊,沒辦法,只能自己寫一個了。
在網上查閱了相關方法後,獲取CPU使用率主要有兩種方法。一個是利用adb top命令;另一個就是讀取/proc/stat檔案,然後解析相關引數,自己去計算。
方法一、解析top命令結果
這是執行adb shell top命令後的結果。
從圖中可知,top命令的執行結果顯示,在第一個非空白行上有各部分的CPU佔用率,我們只要稍微整理下就行。
先上程式碼:
然而得到的結果顯然不對,以下是從輸入流中打印出來的命令執行結果:
顯然,系統對於這個top命令動態的進行了修改,防止資訊被App獲取。正確的執行許可權只留給了adbshell。
至此,此方法失效。
方法二、解析/proc/stat檔案
/proc/stat檔案動態記錄所有CPU活動的資訊,該檔案中的所有值都是從系統啟動開始累計到當前時刻。所以,計算系統當前的CPU佔用率的方法就是,計算在間隔較短(ms級)的時間內,cpu的各活動資訊的變化量,作為當前的實時CPU佔用率。
下圖是執行shell命令,獲取檔案的內容。
其中,以CPU開頭的兩行表示的資訊就是,當前該CPI的一個總的使用情況,後面各個數值的單位為jiffies,可以簡單理解為Linux中作業系統程序排程的最小時間片。具體含義如下(以CPU0為例):
user(181596)從系統啟動開始累計到當前時刻,處於使用者態的執行時間,不包含 nice值為負程序。;
nice(85733)從系統啟動開始累計到當前時刻,nice值為負的程序所佔用的CPU時間;
system (197165)從系統啟動開始累計到當前時刻,處於核心態的執行時間;
idle (1328127)從系統啟動開始累計到當前時刻,除IO等待時間以外的其它等待時間;
iowait(11679)從系統啟動開始累計到當前時刻,IO等待時間;
irq (5)從系統啟動開始累計到當前時刻,硬中斷時間;
softirq (5138)從系統啟動開始累計到當前時刻,軟中斷時間。
這裡,我們只需關心“idle”,它表示了系統的空閒時間,以及各項數值之和就是CPU的總消耗。
因此,我們以totalJiffies1表示第一次CPU總消耗,totalIdle1表示第一次的CPU空閒時間,同理,totalJiffies2、totalIdle2表示第二次的相關資訊,則cpu的佔用率如下:
double cpuRate=1.0*((totalIdle2-totalJiffies2)-(totalIdle1-totalJiffies1))/( totalIdle2- totalIdle1);
以下是根據這個思想實現的程式碼:
/**獲取當前CPU佔比
* 在實際測試中發現,有的手機會隱藏CPU狀態,不會完全顯示所有CPU資訊,例如MX5,所有建議只做參考
* @return
*/
public static String getCPURateDesc(){
String path = "/proc/stat";// 系統CPU資訊檔案
long totalJiffies[]=new long[2];
long totalIdle[]=new long[2];
int firstCPUNum=0;//設定這個引數,這要是防止兩次讀取檔案獲知的CPU數量不同,導致不能計算。這裡統一以第一次的CPU數量為基準
FileReader fileReader = null;
BufferedReader bufferedReader = null;
Pattern pattern=Pattern.compile(" [0-9]+");
for(int i=0;i<2;i++) {
totalJiffies[i]=0;
totalIdle[i]=0;
try {
fileReader = new FileReader(path);
bufferedReader = new BufferedReader(fileReader, 8192);
int currentCPUNum=0;
String str;
while ((str = bufferedReader.readLine()) != null&&(i==0||currentCPUNum<firstCPUNum)) {
if (str.toLowerCase().startsWith("cpu")) {
currentCPUNum++;
int index = 0;
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
try {
long tempJiffies = Long.parseLong(matcher.group(0).trim());
totalJiffies[i] += tempJiffies;
if (index == 3) {//空閒時間為該行第4條欄目
totalIdle[i] += tempJiffies;
}
index++;
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
if(i==0){
firstCPUNum=currentCPUNum;
try {//暫停50毫秒,等待系統更新資訊。
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
double rate=-1;
if (totalJiffies[0]>0&&totalJiffies[1]>0&&totalJiffies[0]!=totalJiffies[1]){
rate=1.0*((totalJiffies[1]-totalIdle[1])-(totalJiffies[0]-totalIdle[0]))/(totalJiffies[1]-totalJiffies[0]);
}
return String.format("cpu:%.2f",rate);
}
思考:上述方法中,為了獲取兩次CPU資訊,強制執行緒休眠了50ms,若實際檔案內容的修改要比這個時間多怎麼辦?這樣兩次讀取的資訊就相同了;如果少於50ms,則白等了這麼久。因此引入了FileObserve,當檔案被修改時,系統回撥相應的方法通知我們去讀取新的內容,這樣就可以避免上述問題。
方法很簡單,就不全貼了,就附上變化的部分:
1、繼承FileObserver類,並實現onEvent(int event, String path)方法,在執行該類的startWatching()方法後,系統會回撥onEvent方法,告知該檔案發生變化的情況,當判定為檔案被修改時,就喚醒讀取檔案的執行緒,繼續工作。
2、當第一次讀取到檔案輸入流後,啟動檔案監聽。
3、當第一次讀取解析檔案結束後,不再是執行sleep方法,而是執行wait方法,等待接收到系統的回撥。
結果:第二次檔案讀取一直未進行。
通過日誌發現,接收到的檔案變化事件只有三類:
即:
即,檔案只發生過訪問資料、開啟檔案、關閉檔案這三種操作。
這明顯不可能,檔案的內容一直在動態變化著!
通過上網查詢資料發現:/proc檔案系統是一個偽檔案系統,它只存在記憶體當中,而不佔用外存空間。它以檔案系統的方式為核心與程序提供通訊的介面。使用者和應用程式可以通過/proc得到系統的資訊,並可以改變核心的某些引數。由於系統的資訊,如程序,是動態改變的,所以使用者或應用程式讀取/proc目錄中的檔案時,proc檔案系統是動態從系統核心讀出所需資訊並提交的。
也就是說,這是一個特殊的檔案,是收不到檔案修改事件的,想通過檔案觀察物件進行監督檔案的修改情況的方法也就失效了。
總結:獲取CPU使用率資訊,目前相對最可靠的方式就是兩次獲取並/proc/stat檔案內容,然後計算。