Google《Android性能優(yōu)化》學(xué)習(xí)筆記

渲染篇

  1. 大多數(shù)手機(jī)的屏幕刷新頻率是60HZ,如果在1000/60=16.67ms內(nèi)沒有辦法把這一幀的任務(wù)執(zhí)行完畢,就會(huì)發(fā)生丟幀的現(xiàn)象。丟幀越多,用戶感受的卡頓情況就越嚴(yán)重。
  2. 渲染操作通常依賴于兩個(gè)核心組件:CPU與GPU。CPU負(fù)責(zé)包括Measure,Layout,Record,Execute的計(jì)算操作,GPU負(fù)責(zé)Rasterization(柵格化)操作。CPU通常存在的問題的原因是存在非必需的視圖組件,它不僅僅會(huì)帶來重復(fù)的計(jì)算操作,而且還會(huì)占用額外的GPU資源。

Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎(chǔ)的操作。它把那些組件拆分到不同的像素上進(jìn)行顯示。這是一個(gè)很費(fèi)時(shí)的操作,GPU的引入就是為了加快柵格化的操作。

  1. CPU負(fù)責(zé)把UI組件計(jì)算成Polygons,Texture紋理,然后交給GPU進(jìn)行柵格化渲染。
  2. Overdraw(過度繪制)描述的是屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次。
  3. 減少過度繪制的步驟:
  • 移除Window默認(rèn)的background
  • XML布局中去除多余的background(一般父布局中的background可以不設(shè)置,具體視情況而定)
  1. 自定義view時(shí),需注意有重疊部分時(shí)采用ClipRect方法,避免重疊部分被過度繪制
  2. 提升布局性能的關(guān)鍵點(diǎn)時(shí)盡量保持布局層級(jí)的扁平化,避免出現(xiàn)重復(fù)的嵌套布局。

運(yùn)算篇

  1. TraceView使用了解
  2. Batching and Caching

Batching是在真正執(zhí)行運(yùn)算操作之前對(duì)數(shù)據(jù)進(jìn)行批量預(yù)處理,例如你需要有這樣一個(gè)方法,它的作用是查找某個(gè)值是否存在于一堆數(shù)據(jù)中。

  1. 把可能有性能問題的代碼放到非主線程中。
  2. Vector,ArrayList,LinkedList,HashMap,SparseArray

內(nèi)存篇

  1. Android系統(tǒng)里面有一個(gè)Generatioal Heap Memory的模型,系統(tǒng)會(huì)根據(jù)不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的GC操作。例如,最近剛分配的對(duì)象會(huì)放在Young Generation區(qū)域,這個(gè)區(qū)域的對(duì)象通常都是會(huì)快速被創(chuàng)建并且很快被銷毀回收的,同時(shí)這個(gè)區(qū)域的GC操作速度也是比Old Generation區(qū)域的GC操作速度更快的。除了速度差異之外,執(zhí)行GC操作的時(shí)候,所有線程的任何操作都會(huì)需要暫停,等待GC操作完成之后,其他操作才能夠繼續(xù)運(yùn)行。通常來說,單個(gè)的GC并不會(huì)占用太多時(shí)間,但是大量不停的GC操作則會(huì)顯著占用幀間隔時(shí)間(16ms)。如果在幀間隔時(shí)間里面做了過多的GC操作,那么自然其他類似計(jì)算,渲染等操作的可用時(shí)間就變得少了。
  2. memory monitor工具
  3. 發(fā)生內(nèi)存泄漏會(huì)導(dǎo)致Memory Generation中的剩余可用Heap Size越來越小,這樣會(huì)導(dǎo)致頻繁觸發(fā)GC,更進(jìn)一步引起性能問題。

內(nèi)存泄漏表示的是不再用到的對(duì)象因?yàn)楸诲e(cuò)誤引用而無法進(jìn)行回收。

  1. Memory Churn內(nèi)存抖動(dòng),內(nèi)存抖動(dòng)是因?yàn)樵诙虝r(shí)間內(nèi)大量的對(duì)象被創(chuàng)建又馬上被釋放。瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候,會(huì)觸發(fā)GC從而導(dǎo)致剛產(chǎn)生的對(duì)象又很快被回收。即使每次分配的對(duì)象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問題。

解決上面的問題有簡潔直觀方法,如果你在Memory Monitor里面查看到短時(shí)間發(fā)生了多次內(nèi)存的漲跌,這意味著很有可能發(fā)生了內(nèi)存抖動(dòng)。同時(shí)我們還可以通過Allocation Tracker來查看在短時(shí)間內(nèi),同一個(gè)棧中不斷進(jìn)出的相同對(duì)象。這是內(nèi)存抖動(dòng)的典型信號(hào)之一。

當(dāng)你大致定位問題之后,接下去的問題修復(fù)也就顯得相對(duì)直接簡單了。例如,你需要避免在for循環(huán)里面分配對(duì)象占用內(nèi)存,需要嘗試把對(duì)象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發(fā)生繪制以及動(dòng)畫執(zhí)行過程中,onDraw方法都會(huì)被調(diào)用到,避免在onDraw方法里面執(zhí)行復(fù)雜的操作,避免創(chuàng)建對(duì)象。對(duì)于那些無法避免需要?jiǎng)?chuàng)建對(duì)象的情況,我們可以考慮對(duì)象池模型,通過對(duì)象池來解決頻繁創(chuàng)建與銷毀的問題,但是這里需要注意結(jié)束使用之后,需要手動(dòng)釋放對(duì)象池中的對(duì)象。

  1. 關(guān)于Allocation Tracker工具的使用,不展開了,參考下面的鏈接:
  1. 三種測量內(nèi)存工具,各自的特點(diǎn):
  • Memory Monitor:跟蹤整個(gè)APP內(nèi)存變化情況。
  • Heap Viewer:查看當(dāng)前內(nèi)存快照,便于對(duì)比分析哪些對(duì)象有可能發(fā)生內(nèi)存泄露
  • Alloction Tracker:追蹤內(nèi)存對(duì)象的來源

電量篇

  1. 使用WakeLock或者JobScheduler喚醒設(shè)備處理定時(shí)的任務(wù)之后,一定要及時(shí)讓設(shè)備回到初始狀態(tài)。每次喚醒蜂窩信號(hào)進(jìn)行數(shù)據(jù)傳遞,都會(huì)消耗很多電量,它比WiFi等操作更加的耗電。
  2. 我們可以通過下面的代碼來獲取手機(jī)的當(dāng)前充電狀態(tài):
// It is very easy to subscribe to changes to the battery state, but you can get the current
// state by simply passing null in as your receiver.  Nifty, isn't that?
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, filter);
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
if (acCharge) {
    Log.v(LOG_TAG,“The phone is charging!”);
}

在上面的例子演示了如何立即獲取到手機(jī)的充電狀態(tài),得到充電狀態(tài)信息之后,我們可以有針對(duì)性的對(duì)部分代碼做優(yōu)化。比如我們可以判斷只有當(dāng)前手機(jī)為AC充電狀態(tài)時(shí) 才去執(zhí)行一些非常耗電的操作。

/**
 * This method checks for power by comparing the current battery state against all possible
 * plugged in states. In this case, a device may be considered plugged in either by USB, AC, or
 * wireless charge. (Wireless charge was introduced in API Level 17.)
 */
private boolean checkForPower() {
    // It is very easy to subscribe to changes to the battery state, but you can get the current
    // state by simply passing null in as your receiver.  Nifty, isn't that?
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = this.registerReceiver(null, filter);

    // There are currently three ways a device can be plugged in. We should check them all.
    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
    boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
    boolean wirelessCharge = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
    }
    return (usbCharge || acCharge || wirelessCharge);
}
  1. 一個(gè)最簡單的喚醒手機(jī)的方法是使用PowerManager.WakeLock的API來保持CPU工作并防止屏幕變暗關(guān)閉。這使得手機(jī)可以被喚醒,執(zhí)行工作,然后回到睡眠狀態(tài)。知道如何獲取WakeLock是簡單的,可是及時(shí)釋放WakeLock也是非常重要的,不恰當(dāng)?shù)氖褂肳akeLock會(huì)導(dǎo)致嚴(yán)重錯(cuò)誤。例如網(wǎng)絡(luò)請求的數(shù)據(jù)返回時(shí)間不確定,導(dǎo)致本來只需要10s的事情一直等待了1個(gè)小時(shí),這樣會(huì)使得電量白白浪費(fèi)了。這也是為何使用帶超時(shí)參數(shù)的wakelock.acquice()方法是很關(guān)鍵的。

但是僅僅設(shè)置超時(shí)并不足夠解決問題,例如設(shè)置多長的超時(shí)比較合適?什么時(shí)候進(jìn)行重試等等?解決上面的問題,正確的方式可能是使用非精準(zhǔn)定時(shí)器。通常情況下,我們會(huì)設(shè)定一個(gè)時(shí)間進(jìn)行某個(gè)操作,但是動(dòng)態(tài)修改這個(gè)時(shí)間也許會(huì)更好。例如,如果有另外一個(gè)程序需要比你設(shè)定的時(shí)間晚5分鐘喚醒,最好能夠等到那個(gè)時(shí)候,兩個(gè)任務(wù)捆綁一起同時(shí)進(jìn)行,這就是非精確定時(shí)器的核心工作原理。我們可以定制計(jì)劃的任務(wù),可是系統(tǒng)如果檢測到一個(gè)更好的時(shí)間,它可以推遲你的任務(wù),以節(jié)省電量消耗。這正是JobScheduler API所做的事情

JobScheduler會(huì)根據(jù)當(dāng)前的情況與任務(wù),組合出理想的喚醒時(shí)間,例如等到正在充電或者連接到WiFi的時(shí)候,或者集中任務(wù)一起執(zhí)行。我們可以通過這個(gè)API實(shí)現(xiàn)很多免費(fèi)的調(diào)度算法。

  1. 為了減少電量的消耗,在蜂窩移動(dòng)網(wǎng)絡(luò)下,最好做到批量執(zhí)行網(wǎng)絡(luò)請求,盡量避免頻繁的間隔網(wǎng)絡(luò)請求。WiFi連接下,網(wǎng)絡(luò)傳輸?shù)碾娏肯囊纫苿?dòng)網(wǎng)絡(luò)少很多,應(yīng)該盡量減少移動(dòng)網(wǎng)絡(luò)下的數(shù)據(jù)傳輸,多在WiFi環(huán)境下傳輸數(shù)據(jù)。
  2. 使用Job Scheduler,應(yīng)用需要做的事情就是判斷哪些任務(wù)是不緊急的,可以交給Job Scheduler來處理,Job Scheduler集中處理收到的任務(wù),選擇合適的時(shí)間,合適的網(wǎng)絡(luò),再一起進(jìn)行執(zhí)行。

下面是使用Job Scheduler的一段簡要示例,需要先有一個(gè)JobService

public class MyJobService extends JobService {
    private static final String LOG_TAG = "MyJobService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG_TAG, "MyJobService created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOG_TAG, "MyJobService destroyed");
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        // This is where you would implement all of the logic for your job. Note that this runs
        // on the main thread, so you will want to use a separate thread for asynchronous work
        // (as we demonstrate below to establish a network connection).
        // If you use a separate thread, return true to indicate that you need a "reschedule" to
        // return to the job at some point in the future to finish processing the work. Otherwise,
        // return false when finished.
        Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId());
        // First, check the network, and then attempt to connect.
        if (isNetworkConnected()) {
            new SimpleDownloadTask() .execute(params);
            return true;
        } else {
            Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
        }
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // Called if the job must be stopped before jobFinished() has been called. This may
        // happen if the requirements are no longer being met, such as the user no longer
        // connecting to WiFi, or the device no longer being idle. Use this callback to resolve
        // anything that may cause your application to misbehave from the job being halted.
        // Return true if the job should be rescheduled based on the retry criteria specified
        // when the job was created or return false to drop the job. Regardless of the value
        // returned, your job must stop executing.
        Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId());
        return false;
    }

    /**
     * Determines if the device is currently online.
     */
    private boolean isNetworkConnected() {
        ConnectivityManager connectivityManager =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        return (networkInfo != null && networkInfo.isConnected());
    }

    /**
     *  Uses AsyncTask to create a task away from the main UI thread. This task creates a
     *  HTTPUrlConnection, and then downloads the contents of the webpage as an InputStream.
     *  The InputStream is then converted to a String, which is logged by the
     *  onPostExecute() method.
     */
    private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {

        protected JobParameters mJobParam;

        @Override
        protected String doInBackground(JobParameters... params) {
            // cache system provided job requirements
            mJobParam = params[0];
            try {
                InputStream is = null;
                // Only display the first 50 characters of the retrieved web page content.
                int len = 50;

                URL url = new URL("https://www.google.com");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(10000); //10sec
                conn.setConnectTimeout(15000); //15sec
                conn.setRequestMethod("GET");
                //Starts the query
                conn.connect();
                int response = conn.getResponseCode();
                Log.d(LOG_TAG, "The response is: " + response);
                is = conn.getInputStream();

                // Convert the input stream to a string
                Reader reader = null;
                reader = new InputStreamReader(is, "UTF-8");
                char[] buffer = new char[len];
                reader.read(buffer);
                return new String(buffer);

            } catch (IOException e) {
                return "Unable to retrieve web page.";
            }
        }

        @Override
        protected void onPostExecute(String result) {
            jobFinished(mJobParam, false);
            Log.i(LOG_TAG, result);
        }
    }
}

然后模擬通過點(diǎn)擊Button觸發(fā)N個(gè)任務(wù),交給JobService來處理:

public class FreeTheWakelockActivity extends ActionBarActivity {
    public static final String LOG_TAG = "FreeTheWakelockActivity";

    TextView mWakeLockMsg;
    ComponentName mServiceComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wakelock);

        mWakeLockMsg = (TextView) findViewById(R.id.wakelock_txt);
        mServiceComponent = new ComponentName(this, MyJobService.class);
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        startService(startServiceIntent);

        Button theButtonThatWakelocks = (Button) findViewById(R.id.wakelock_poll);
        theButtonThatWakelocks.setText(R.string.poll_server_button);

        theButtonThatWakelocks.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    pollServer();
            }
        });
    }

    /**
     * This method polls the server via the JobScheduler API. By scheduling the job with this API,
     * your app can be confident it will execute, but without the need for a wake lock. Rather, the
     * API will take your network jobs and execute them in batch to best take advantage of the
     * initial network connection cost.
     *
     * The JobScheduler API works through a background service. In this sample, we have
     * a simple service in MyJobService to get you started. The job is scheduled here in
     * the activity, but the job itself is executed in MyJobService in the startJob() method. For
     * example, to poll your server, you would create the network connection, send your GET
     * request, and then process the response all in MyJobService. This allows the JobScheduler API
     * to invoke your logic without needed to restart your activity.
     *
     * For brevity in the sample, we are scheduling the same job several times in quick succession,
     * but again, try to consider similar tasks occurring over time in your application that can
     * afford to wait and may benefit from batching.
     */
    public void pollServer() {
        JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        for (int i=0; i<10; i++) {
            JobInfo jobInfo = new JobInfo.Builder(i, mServiceComponent)
                    .setMinimumLatency(5000) // 5 seconds
                    .setOverrideDeadline(60000) // 60 seconds (for brevity in the sample)
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // WiFi or data connections
                    .build();

            mWakeLockMsg.append("Scheduling job " + i + "!\n");
            scheduler.schedule(jobInfo);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容