Android APP耗电优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lhd201006/article/details/52851366

可能造成耗电的一些原因

  • 网络请求耗电,而且手机数据网络进行http请求比无线网进行http请求更加耗电,因为数据网络调用到一些底层的硬件模块,就如GPS一样,当手机打开GPS功能后,也是启动了一些硬件模块就会明显增加耗电
  • 高频的刷新UI界面,刷新UI界面其实就是进行layout的绘制,如果一个Activity的布局嵌套太多层,那么每一层layout都会刷新一次,例如动画等等这些都会造成耗电
  • 数据库,SD卡文件操作,这些都是属于耗时操作,当操作次数很少的时候基本不会有耗电问题,但是当短时间内操作次数很多的话,也会明显的增加耗电,同时也有可能造成页面卡顿
  • AlarmManager,例如一些推送的心跳包实现,AlarmManager会定时唤醒CPU去执行一些任务,也是造成耗电的一大源头
  • 手机网络环境不好的时候会频繁的切换网络,因为网络数据交互的时候,系统也是会被唤醒的,所以APP如果在监听了网络切换广播后做了大量的操作,一样会增加耗电
  • 针对一些任务队列的处理,如果队列堆积的任务太多,导致循环执行太久也会造成耗电,因为占用了CPU资源去执行代码,我们的log日志工具保存到文件就是用任务队列实现的,当压力测试SDK一次性接受1万条消息的时候,那内存就表上来了,跟了下发现日志保存队列里面积压了4千多个任务,这时候即使手机锁屏,也还会不断的把队列中的任务执行完然后CPU才会休眠下去的,同样会造成严重的耗电,耗内存,好在release版本的日志都是关闭的
  • 执行一些高运算量的代码,例如json数据解析,一些二进制协议的数据编码和解码
  • 接收系统的一分钟广播,然后做一些程序逻辑处理,其实接收一分钟广播不耗电,耗电的是一分钟执行一次程序处理
  • Wake Lock使用不当导致没有及时的释放,Wake Lock可以阻止cpu进入休眠的,如果没有及时的release会造成cpu无法休眠,程序耗电严重
  • 如果程序中有定时任务,在cpu休眠之后,定时任务就会被挂起不执行,这时候并不会造成太大的耗电,但是如果这个定时任务的时间间隔很短,1秒执行一次,那么当手机app集成了推送,推送就会有心跳包通过AlarmManager来唤醒,每次唤醒的时候就会再去执行挂起的定时任务,虽然执行定时任务的耗电量可能比心跳包的耗电量少很多,不过还是需要注意的,积少成多
  • 其实android中的Log日志的打印也会耗电的,在日常开发中,我们可能不仅会把log打印到AndroidStudio里面,有可能还会保存起来,而且可能在打印对象信息数据的时候会用到json格式转换,这些都会增加耗电,但是在正式发布的apk包中日志管理一般都是关闭的
  • 在手机锁屏后,CPU会过一段时间才休眠,如果程序中有定时任务,在CPU休眠后会被挂起不执行,但是在CPU休眠之前,定时任务还是会一直的执行的,之前遇到过这么一个问题,我们采用Picasso库:Picasso.with(context)
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);
    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    this.targetToAction = new WeakHashMap<Object, Action>();
    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    this.referenceQueue = new ReferenceQueue<Object>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
  }

在Picasso构造的过程中:启动了一个线程CleanupThread

private static class CleanupThread extends Thread {
    private final ReferenceQueue<Object> referenceQueue;
    private final Handler handler;

    CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
      this.referenceQueue = referenceQueue;
      this.handler = handler;
      setDaemon(true);
      setName(THREAD_PREFIX + "refQueue");
    }

    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      while (true) {
        try {
          // Prior to Android 5.0, even when there is no local variable, the result from
          // remove() & obtainMessage() is kept as a stack local variable.
          // We're forcing this reference to be cleared and replaced by looping every second
          // when there is nothing to do.
          // This behavior has been tested and reproduced with heap dumps.
          RequestWeakReference<?> remove =
              (RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS);
          Message message = handler.obtainMessage();
          if (remove != null) {
            message.what = REQUEST_GCED;
            message.obj = remove.action;
            handler.sendMessage(message);
          } else {
            message.recycle();
          }
        } catch (InterruptedException e) {
          break;
        } catch (final Exception e) {
          handler.post(new Runnable() {
            @Override public void run() {
              throw new RuntimeException(e);
            }
          });
          break;
        }
      }
    }

    void shutdown() {
      interrupt();
    }
  }

CleanupThread线程里面是有一个while(true){}循环,这个就会在手机锁屏后持续运行一段时间才会被挂起的,这个过程就增加了耗电,然后打开查看CPU使用情况,发现app一直被占用一点点,正常情况下,app的退到后台后,cpu应该是不会一直被占用的,如下图:
这里写图片描述
包名com.xtc.watch在退到后台之后仍然在占用CPU的资源执行一些东西,然后打开AndroidStudio,查看monitor里面的cpu的方法跟踪功能(Start method tracing)
方法跟踪功能,红色部分就表示cpu被占用的比例,过一段时间后再次点击Start Method Tracing按钮停止方法跟踪,然后AndroidStudio会自动生成一个后缀名为.trace的分析文件:
方法执行线程信息
上图中Thread后面的是执行方法所在线程的名称,Wall Clock Time表示程序从运行到终止所花费的时间,Thread Time是线程执行的时长,CPU Time是CPU的运行时间,在Thread列表里面的就表示在方法跟踪期间有执行过的线程,下面有颜色的长条是线程执行的时间信息Wall clock time和cpu time,如果在程序退到后台,手机锁屏后,仍然不断有程序里面的线程在跑,那么就存在了耗电风险了,你想想,程序退到后台,手机锁屏了,这时候所有程序线程有在方法跟踪期间的县城都已经展示出来了,如果发现程序耗电,很可能就是由于线程还在不断的运行造成的
- 在Service里面执行一些循环代码,也
可能造成耗电;如果Service是前台Service,那Service的进程优先级会比较高,或者是一些后台常驻Service,在这些Service执行无限循环的代码,耗电耗到手机发烫:

public class PushService extends Service {

    private static final String tag = PushService.class.getSimpleName();

    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    private Queue<Runnable> taskQueue = new LinkedList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(tag, this.getPackageName() + ":" + this.getClass().getSimpleName() + " onCreate.");
        Runnable task = null;
        while (!executorService.isShutdown()) {
            if ((task = taskQueue.poll()) != null) {
                task.run();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(tag, this.getPackageName() + ":" + this.getClass().getSimpleName() + " onDestroy.");
        executorService.shutdownNow();
    }
}

while循环里面的poll()方法并不是阻阻塞的,因此会一直重复的跑这段代码导致CUP即使在手机锁屏的时候仍然高速的在运行着循环代码

针对以上原因进行相应的优化

  • 针对Http请求:
    1. 对http请求数据做GZIP压缩,当前流行的http第三方看默认都支持GZIP压缩
    2. Http缓存,Http协议有一个Cache机制,当发出http请求的时候会先到指定目录下检查是否已经存在这个请求的数据,如果存在并且还没过时,那么就会直接返回;而一些第三方例如OkHttp也有有自己的缓存机制OKHTTP缓存
    3. 合并Http请求来减少Http请求次数,因为Http底层也是TCP连接,对于每个Http请求,发出请求的时候都会创建TCP连接,请求结束后会断开TCP连接,那么当Http请求次数很多的时候就会频繁的创建和断开TCP连接,如果把当中一些请求进行合理的合并,那么就会减少Http请求次数
    4. 制定合理的Http请求数据格式和返回数据格式,做到请求数据中没有冗余字段
    5. 可以在Http请求数据格式里面加一个字段dataVersion代表本地已有数据的版本号,然后传到服务器,服务器的数据表中也有一个字段是dataVersion,当服务器数据被修改的时候,dataVersion就加一,当检测到客户端传上来的dataVersion小于服务器数据表中的dataVersion的时候就返回最新数据,否则可以直接返回空数据代表当前本地数据已经是最新数据,这样就不会每次请求http的时候都会返回大量数据,当数据没有被改变的时候直接返回空,减少了http请求过程中的数据交互(但是要考虑一点,只有一些请求数据量比较大的才适合,因为增加了dataVersion字段后无论给客户端还是服务端也都相应的增加了维护的成本)
  • 针对数据库,SD卡文件操作:
    1. APP在对数据库或者SD卡文件操作的时候无非也是涉及到一些数据的转换,json转换,可以采用json解析效率高的第三方库,例如fast json,Jackson,gson
    2. 可以把一些需要持久化到数据库或者文件中的数据先缓存在内存中,然后在一个时间点一起触发一同更新到数据库或者文件中;例如,在进入Activity的时候会首先从数据库中搜索出帐户信息并且展示在界面,然后会再去发Http请求服务器的帐户信息数据,再把服务器最新的帐户信息数据刷新到界面中,同时也存在一个内存对象中,这时候先不更新到数据库,当退出这个Activity界面的时候可以去检测帐户数据是否发生改变,如果改变了就更新到数据中;这么做的好处是:如果在Activity界面多次修改数据,那最新的数据都是只更新到内存中的,当Activity退出后才把最新的帐户信息数据更新到数据库中,没必要更改一次就同时把数据更新一次到数据库
    3. SD卡的文件读写操作比数据库要快,数据库也是属于文件,但是在写数据的时候还要经过sqlite的一些列数据库操作,但是SD卡在写数据的时候是直接写到文件中的,所以针对与简单数据,标志变量或者数据条数很少的数据,而且安全性要求也不高的数据可以直接存在文件中,例如SharedPreferences中,只有关系型的数据,数据安全性较高的,数据记录条数比较多的可以选择数据库存储,而且如果数据库进行加密后,对于数据库的读写操作会更慢了
    4. 针对数据库操作尽量不要直接使用android里面的Sqlite来手写读写的那些sql语句,可以选择一些orm框架库,例如ormlite,GreenDao等等,我们选择的是ormlite,因为ormlite数据库加密已经有现成的库可以提供使用

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试