天天看點

Android系統CPU使用率擷取(附java代碼)

若想直接看有效方法,請閱讀方法二,第一條。

最近因為一個需求,需要記錄下目前的CPU使用率,在翻遍了API後,發現系統并沒有給予一個方法,能夠簡單的擷取相關CPU資訊,沒辦法,隻能自己寫一個了。

在網上查閱了相關方法後,擷取CPU使用率主要有兩種方法。一個是利用adb top指令;另一個就是讀取/proc/stat檔案,然後解析相關參數,自己去計算。

方法一、解析top指令結果

這是執行adb shell top指令後的結果。

Android系統CPU使用率擷取(附java代碼)

從圖中可知,top指令的執行結果顯示,在第一個非空白行上有各部分的CPU占用率,我們隻要稍微整理下就行。

先上代碼:

Android系統CPU使用率擷取(附java代碼)

然而得到的結果顯然不對,以下是從輸入流中列印出來的指令執行結果:

Android系統CPU使用率擷取(附java代碼)

顯然,系統對于這個top指令動态的進行了修改,防止資訊被App擷取。正确的執行權限隻留給了adbshell。

至此,此方法失效。

方法二、解析/proc/stat檔案

/proc/stat檔案動态記錄所有CPU活動的資訊,該檔案中的所有值都是從系統啟動開始累計到目前時刻。是以,計算系統目前的CPU占用率的方法就是,計算在間隔較短(ms級)的時間内,cpu的各活動資訊的變化量,作為目前的實時CPU占用率。

下圖是執行shell指令,擷取檔案的内容。

Android系統CPU使用率擷取(附java代碼)

其中,以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方法,告知該檔案發生變化的情況,當判定為檔案被修改時,就喚醒讀取檔案的線程,繼續工作。

Android系統CPU使用率擷取(附java代碼)

2、當第一次讀取到檔案輸入流後,啟動檔案監聽。

Android系統CPU使用率擷取(附java代碼)

3、當第一次讀取解析檔案結束後,不再是執行sleep方法,而是執行wait方法,等待接收到系統的回調。

Android系統CPU使用率擷取(附java代碼)

結果:第二次檔案讀取一直未進行。

通過日志發現,接收到的檔案變化事件隻有三類:

Android系統CPU使用率擷取(附java代碼)

即:

Android系統CPU使用率擷取(附java代碼)

即,檔案隻發生過通路資料、打開檔案、關閉檔案這三種操作。

這明顯不可能,檔案的内容一直在動态變化着!

通過上網查找資料發現:/proc檔案系統是一個僞檔案系統,它隻存在記憶體當中,而不占用外存空間。它以檔案系統的方式為核心與程序提供通信的接口。使用者和應用程式可以通過/proc得到系統的資訊,并可以改變核心的某些參數。由于系統的資訊,如程序,是動态改變的,是以使用者或應用程式讀取/proc目錄中的檔案時,proc檔案系統是動态從系統核心讀出所需資訊并送出的。

也就是說,這是一個特殊的檔案,是收不到檔案修改事件的,想通過檔案觀察對象進行監督檔案的修改情況的方法也就失效了。

總結:擷取CPU使用率資訊,目前相對最可靠的方式就是兩次擷取并/proc/stat檔案内容,然後計算。