Android性能优化-方法耗时

文章目录

    • 方法耗时
    • CPU Profiler
    • 使用辅助类来帮助执行时间长的方法
      • HandlerThread
      • IntentService
      • AsyncTask
      • 线程池
    • 在执行次数多的方法中应避免的事项


方法耗时

在我之前的博客如何优雅的检测主线程中的耗时方法中分析了:利用Android系统的消息机制原理去检测主线程中的耗时方法,其实对于执行方法引起的性能开销主要分两类:

  • 执行时间长的方法。
  • 执行次数多的方法。

对于这两类方法,可以使用工具Traceview(在Android Studio 3.0 版本和以下版本中使用)和CPU Profiler(Android Studio 3.1版本以上使用)进行分析。

由于Traceview已经被抛弃,所以这里不再做介绍。


CPU Profiler

关于CPU Profiler,官方提供了中文文档,清查看:使用 CPU Profiler 检查 CPU Activity 和函数跟踪。

例子:

模拟在Activity中执行时间长的方法和次数多的方法:

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

        findViewById(R.id.activity_main_test1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                method1();
            }
        });

        findViewById(R.id.activity_main_test2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                for (int i = 0; i < 20; i++) {
                    method2();
                }
            }
        });

    }
//执行次数多的方法
    private void method2() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
   // 执行时间长的方法
    private void method1() {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

上面代码主要是测试点击按钮让主线程睡一秒的两种方法,使用CPU Profiler分析如下:

Call Chart 标签:

Android性能优化-方法耗时_第1张图片
图1.Call Chart 标签

Call Chart 标签提供函数跟踪的图形表示形式,其中,水平轴表示函数调用(或调用方)的时间段和时间,并沿垂直轴显示其被调用者。 对系统 API 的函数调用显示为橙色,对应用自有函数的调用显示为绿色,对第三方 API(包括 Java 语言 API)的函数调用显示为蓝色

通过上面图1中的Call Chart 标签,可以看到函数从上到下的栈调用轨迹:

com.android.internal.os.ZygoteLnit.main ->......-> android.app.ActivityThread,main -> ...... android.os.Looper.loop -> ...... -> MainActivity.onClick -> ...... -> MainActivity.method1 -> ......

Flame Chart 标签:

Android性能优化-方法耗时_第2张图片
图2.Flame Chart 标签

上面图2中显示了Flame Chart 标签,Flame Chart 标签提供一个倒置的调用图表,其汇总相同的调用堆栈。 即,收集共享相同调用方顺序的完全相同的函数,并在火焰图中用一个较长的横条表示它们(而不是将它们显示为多个较短的横条,如调用图表中所示)。 这样更方便您查看哪些函数消耗最多时间。 不过,这也意味着水平轴不再代表时间线,相反,它表示每个函数相对的执行时间。

调用Thread类方法的相同调用堆栈:

android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method1 -> Thread.sleep -> ......
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method2 -> Thread.sleep -> ......

调用MainActivity类方法的相同调用堆栈:

android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method1 
android.os.Handler.dispatchMessage -> ...... -> android.view.View.performClick -> MainActivity.onClick -> ...... -> MainActivity.method2

Top Down 标签:

Android性能优化-方法耗时_第3张图片

图3.Top Down 标签

上面图3显示Top Down 标签,Top Down 标签显示一个函数调用列表,在该列表中展开函数节点会显示函数的被调用方。

从上面可以看到method2()方法执行的时间是:1006692us。

另外:Top Down 标签还提供了在每个函数调用上所花费的 CPU 时间(时间也可以用线程总时间占所选时间范围的持续时间的百分比表示):

  • Self: 表示函数调用在执行自己的代码(而非被调用方的代码)上所花的时间,如下图中的函数 D 所示。
  • Children: 表示函数调用在执行自己的被调用方(而非自己的代码)上所花的时间,如下图中的函数 D 所示。
  • 总和: 函数的 Self 和 Children 时间的总和。 这表示应用在执行函数调用上所花的总时间,如下图 中函数 D 所示

[外链图片转存失败(img-so20Ma6q-1562732652066)(https://developer.android.com/studio/images/profile/call_chart_1-2X.png)]

Bottom Up标签:
Android性能优化-方法耗时_第4张图片
图4. Bottom Up标签

上面图4显示了Bottom Up标签, Bottom Up 标签显示一个函数调用列表,在该列表中展开函数节点将显示函数的调用方。

从上面可以看到函数method1的调用方。另外,Bottom Up 标签还用于按照消耗最多(最少)CPU 时间排序函数,上面函数列表从上到下,函数执行时间以此递减。

Top Down 标签和Bottom Up标签的区别:

图5. 一个“Top Down”树。


图6. 图 5 中函数 C 的“Bottom Up”树。

如图 5 所示,在“Top Down”标签中展开函数 A 的节点可显示它的被调用方,即函数 B 和 D。 然后,展开函数 D 的节点可显示它的被调用方,即函数 B 和 C 等等。 与 Flame chart 标签相似,“Top Down”树汇总共享相同调用堆栈的相同函数的跟踪信息。 也就是说,Flame chart 标签可提供Top down 标签的图形化表示形式。

图 6 为函数 C 提供了一个“Bottom Up”树。 在“Bottom Up”树中打开函数 C 的节点可显示它独有的调用方,即函数 B 和 D。 请注意,尽管 B 调用 C 两次,但在“Bottom Up”树中展开函数 C 的节点时,B 仅显示一次。 然后,展开 B 的节点显示其调用方,即函数 A 和 D。


使用辅助类来帮助执行时间长的方法

执行时间长的方法,可以放在子线程中去执行。在Android中提供了IntentService,HandlerThread,AsyncTask类,在Java中也提供了ThreadPoolExecutor类来帮助执行时间长的方法。

HandlerThread

用于启动具有looper的新线程的方便类。 然后可以使用looper来创建处理程序类。 请注意,仍然必须调用start()。

HandlerThread继承自Thread类,在 run方法中创建了一个当前线程的Looper并调用了消息循环loop()方法:

   @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

HandlerThread可以做定时,延时或者无限循环的任务。

IntentService

IntentService是服务的基类,可根据需要处理异步请求(表示为Intents)。 客户端通过Context.startService(Intent)调用发送请求; 服务根据需要启动,使用工作线程依次处理每个Intent,并在工作失败时自行停止

这种“工作队列处理器”模式通常用于从应用程序的主线程卸载任务。 存在IntentService类以简化此模式并处理机制。 要使用它,请扩展IntentService并实现onHandleIntent(Intent)。 IntentService将接收Intents,启动工作线程,并根据需要停止服务。

所有请求都在一个工作线程上处理 - 它们可能需要多长时间(并且不会阻止应用程序的主循环),但一次只能处理一个请求。

由于IntentService是一个Service,所以需要在AndroidManifest.xml配置。

IntentService内部使用了HandlerThread:

    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

  @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

 @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

在HandlerThread类中的子线程中执行任务,任务来自Intent(必须是一个Inteng),执行任务后会停止自己。

IntentService适合执行时间长的耗时任务,比如下载,上传,复杂计算等。

AsyncTask

AsyncTask可以正确,方便地使用UI线程。 此类允许您执行后台操作并在UI线程上发布结果,而无需操作线程和/或处理程序。

AsyncTask被设计为围绕Thread和Handler的助手类,并不构成通用的线程框架。 理想情况下,AsyncTasks应该用于短操作(最多几秒钟)。如果需要保持线程长时间运行,强烈建议您使用java.util.concurrent包提供的各种API,例如 Executor,ThreadPoolExecutor和FutureTask。

异步任务由在后台线程上运行的计算定义,其结果在UI线程上发布。 异步任务由3种泛型类型定义,称为Params,Progress和Result,以及4个步骤,称为onPreExecute,doInBackground,onProgressUpdate和onPostExecute。

AsyncTask内部实现是THREAD_POOL_EXECUTOR+SERIAL_EXECUTOR+Handler:

  • THREAD_POOL_EXECUTOR:可用于并行执行任务的Executor。
  • SERIAL_EXECUTOR:用于任务排队的Executor。
  • Handler:将线程切换到主线程。

执行任务的THREAD_POOL_EXECUTOR:

 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));//线程池大小:最少2个线程,最多4个线程。
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//最大线程数
    private static final int KEEP_ALIVE_SECONDS = 30;//线程闲置时,30秒后被回收
     private static final BlockingQueue sPoolWorkQueue =
            new LinkedBlockingQueue(128);//大小为128的有界队列
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);//核心线程闲置时,超时也会被回收
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

用于任务排队的SERIAL_EXECUTOR:

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static class SerialExecutor implements Executor {
        final ArrayDeque mTasks = new ArrayDeque();//用于任务排队的队列
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();//执行当前任务
                    } finally {
                        scheduleNext();//去执行下一个任务
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();//去执行下一个任务
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

线程池

Executors提供的工厂方法:

  • newFixedThreadPool:线程数量固定的线程池,当线程处于空闲时,它们并不会回收,除非线程池被关闭了。当所有的线程处于活动状态时,新任务都会处于等待状态,直到线程空闲出来。只有核心线程,没有非核心线程,最大线程量为核心线程数量,线程闲置时没有超时时长,使用的队列是LinkedBlockingQueue。
  • newCachedThreadPool:线程数量不定的线程,最大线程数量为Integer.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的线程都有超时机制,这个超时时长为60秒,超过线程就会被回收。 没有核心线程,只有非核心线程,最大线程量为Integer.MAX_VALUE,线程闲置时超时时长为60秒,使用的队列是SynchronoseQueue。
  • newScheduledThreadPool:核心线程数量固定,非核心线程数量没有限制,并且非核心线程闲置时就会被回收。有核心线程,也有非核心线程,最大线程量为Integer.MAX_VALUE,线程闲置时超时时长为0秒,使用的队列是DelayWorkQueue。
  • newSingleThreadExecutor:只有一个核心线程,确保所有的任务都在同一个线程中按顺序执行。有核心线程,没有非核心线程,最大线程量为1,线程闲置时没有超时时长,使用的队列是LinkedBlockingQueue。

创建线程池ThreadPoolExecutor:

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:核心线程。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:线程闲置时的存活时间。
  • unit:存活时间的时间单位。
  • workQueue:存储任务的队列。
  • threadFactory:线程工厂。
  • handler:饱和策略。AbortPolicy (中止策略,默认方式,该策略会抛出未检查异常 RejectedExecutionException), CallerRunsPolicy(调用着运行策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用着,从而降低新任务的流量) , DiscardPolicy(当新提价的任务无法保存到队列中等待执行时,抛弃策略会悄悄抛弃该任务) , DiscardOldestPolicy(抛弃最旧的策略则会抛弃下一个将被执行的任务,然后尝试重新提交新的任务)。

在执行次数多的方法中应避免的事项

1.避免创建大量的对象

在自定义View时,通常会重写onMeasure,onLayout,onDraw方法,这些方法在View的生命周期过程中,通常会被调用多次,所以应该避免在方法中创建大量的对像,能够定义为全局对象的就定义为全局对象。

2.及时移除回调监听接口

ViewTreeObserver.OnGlobalLayoutListener监听器:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //do somthing
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }else{
                    view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            }
        });

RecyclerView.OnScrollListener监听器:

 recyclerView.clearOnScrollListeners();

ViewPager.OnPageChangeListener监听器:

viewPager.clearOnPageChangeListeners();

3.不要做复杂的计算

在频繁的被回调的方法中不要做复杂的计算。

RecyclerView滑动的时候,RecyclerView.OnScrollListener监听器的onScrolled方法会被不断的回调:

 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });

输入文本时,TextWatcher监听器的方法会不断的被回调:

 editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

你可能感兴趣的:(性能优化)