Google《Android性能优化》学习笔记

Google近期在Udacity上发布了Android性能优化的在线课程,分别从渲染,运算与内存,电量几个方面介绍了如何去优化性能,这些课程是Google以前在Youtube上发布的Android性能优化典范专题课程的细化与补充。html

下面是本文做者@胡凯me对渲染、运算、内存、电量篇章的学习笔记,部份内容和前面的性能优化典范有重合,欢迎你们一块儿学习交流!
java

渲染篇

1) Why Rendering Performance Matterspython

如今有很多App为了达到很华丽的视觉效果,会须要在界面上层叠不少的视图组件,可是这会很容易引发性能问题。如何平衡Design与Performance就很须要智慧了。android

2) Defining ‘Jank’git

大多数手机的屏幕刷新频率是60hz,若是在1000/60=16.67ms内没有办法把这一帧的任务执行完毕,就会发生丢帧的现象。丢帧越多,用户感觉到的卡顿状况就越严重。github


3) Rendering Pipeline: Common Problemsweb

渲染操做一般依赖于两个核心组件:CPU与GPU。CPU负责包括Measure,Layout,Record,Execute的计算操做,GPU负责Rasterization(栅格化)操做。CPU一般存在的问题的缘由是存在非必需的视图组件,它不只仅会带来重复的计算操做,并且还会占用额外的GPU资源。算法


4) Android UI and the GPUshell

了解Android是如何利用GPU进行画面渲染有助于咱们更好的理解性能问题。一个很直接的问题是:activity的画面是如何绘制到屏幕上的?那些复杂的XML布局文件又是如何可以被识别并绘制出来的?canvas


Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操做。它把那些组件拆分到不一样的像素上进行显示。这是一个很费时的操做,GPU的引入就是为了加快栅格化的操做。

CPU负责把UI组件计算成Polygons,Texture纹理,而后交给GPU进行栅格化渲染。


然而每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES能够把那些须要渲染的纹理Hold在GPU Memory里面,在下次须要渲染的时候直接进行操做。因此若是你更新了GPU所hold住的纹理内容,那么以前保存的状态就丢失了。

在Android里面那些由主题所提供的资源,例如Bitmaps,Drawables都是一块儿打包到统一的Texture纹理当中,而后再传递到GPU里面,这意味着每次你须要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。固然随着UI组件的愈来愈丰富,有了更多演变的形态。例如显示图片的时候,须要先通过CPU的计算加载到内存中,而后传递给GPU进行渲染。文字的显示比较复杂,须要先通过CPU换算成纹理,而后交给GPU进行渲染,返回到CPU绘制单个字符的时候,再从新引用通过GPU渲染的内容。动画则存在一个更加复杂的操做流程。

为了可以使得App流畅,咱们须要在每帧16ms之内处理完全部的CPU与GPU的计算,绘制,渲染等等操做。

5) GPU Problem: Overdraw

Overdraw(过分绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了屡次。在多层次重叠的UI结构里面,若是不可见的UI也在作绘制的操做,会致使某些像素区域被绘制了屡次。这样就会浪费大量的CPU以及GPU资源。


当设计上追求更华丽的视觉效果的时候,咱们就容易陷入采用复杂的多层次重叠视图来实现这种视觉效果的怪圈。这很容易致使大量的性能问题,为了得到最佳的性能,咱们必须尽可能减小Overdraw的状况发生。

幸运的是,咱们能够经过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw状况。


蓝色、淡绿、淡红、深红表明了4种不一样程度的Overdraw状况,咱们的目标就是尽可能减小红色Overdraw,看到更多的蓝色区域。

6) Visualize and Fix Overdraw - Quiz & Solution

这里举了一个例子,经过XML文件能够看到有好几处非必需的background。经过把XML中非必需的background移除以后,能够显著减小布局的过分绘制。其中一个比较有意思的地方是:针对ListView中的Avatar ImageView的设置,在getView的代码里面,判断是否获取到对应的Bitmap,在获取到Avatar的图像以后,把ImageView的Background设置为Transparent,只有当图像没有获取到的时候才设置对应的Background占位图片,这样能够避免由于给Avatar设置背景图而致使的过分渲染。


总结一下,优化步骤以下:

  • 移除Window默认的Background

  • 移除XML布局文件中非必需的Background

  • 按需显示占位背景图片

7) ClipRect & QuickReject

前面有提到过,对不可见的UI组件进行绘制更新会致使Overdraw。例如Nav Drawer从前置可见的Activity滑出以后,若是还继续绘制那些在Nav Drawer里面不可见的UI组件,这就致使了Overdraw。为了解决这个问题,Android系统会经过避免绘制那些彻底不可见的组件来尽可能减小Overdraw。那些Nav Drawer里面不可见的View就不会被执行浪费资源。


可是不幸的是,对于那些过于复杂的自定义的View(一般重写了onDraw方法),Android系统没法检测在onDraw里面具体会执行什么操做,系统没法监控并自动优化,也就没法避免Overdraw了。可是咱们能够经过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法能够指定一块矩形区域,只有在这个区域内才会被绘制,其余的区域会被忽视。这个API能够很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还能够帮助节约CPU与GPU资源,在clipRect区域以外的绘制指令都不会被执行,那些部份内容在矩形区域内的组件,仍然会获得绘制。


除了clipRect方法以外,咱们还可使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操做。

8) Apply clipRect and quickReject - Quiz & Solution


上面的示例图中显示了一个自定义的View,主要效果是呈现多张重叠的卡片。这个View的onDraw方法以下图所示:


打开开发者选项中的显示过分渲染,能够看到咱们这个自定义的View部分区域存在着过分绘制。那么是什么缘由致使过分绘制的呢?


9) Fixing Overdraw with Canvas API

下面的代码显示了如何经过clipRect来解决自定义View的过分绘制,提升自定义View的绘制性能:


下面是优化事后的效果:


10) Layouts, Invalidations and Perf

Android须要把XML布局文件转换成GPU可以识别并绘制的对象。这个操做是在DisplayList的帮助下完成的。DisplayList持有全部将要交给GPU绘制到屏幕上的数据信息。

在某个View第一次须要被渲染时,Display List会所以被建立,当这个View要显示到屏幕上时,咱们会执行GPU的绘制指令来进行渲染。

若是View的Property属性发生了改变(例如移动位置),咱们就仅仅须要Execute Display List就够了。


然而若是你修改了View中的某些可见组件的内容,那么以前的DisplayList就没法继续使用了,咱们须要从新建立一个DisplayList并从新执行渲染指令更新到屏幕上。


请注意:任什么时候候View中的绘制内容发生变化时,都会须要从新建立DisplayList,渲染DisplayList,更新到屏幕上等一系列操做。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小须要增大到目前的两倍,在增大Button大小以前,须要经过父View从新计算并摆放其余子View的位置。修改View的大小会触发整个HierarcyView的从新计算大小的操做。若是是修改View的位置则会触发HierarchView从新计算其余View的位置。若是布局很复杂,这就会很容易致使严重的性能问题。


11) Hierarchy Viewer: Walkthrough

Hierarchy Viewer能够很直接的呈现布局的层次关系,视图组件的各类属性。 咱们能够经过红,黄,绿三种不一样的颜色来区分布局的Measure,Layout,Executive的相对性能表现如何。

12) Nested Hierarchies and Performance

提高布局性能的关键点是尽可能保持布局层级的扁平化,避免出现重复的嵌套布局。例以下面的例子,有2行显示相同内容的视图,分别用两种不一样的写法来实现,他们有着不一样的层级。



下图显示了使用2种不一样的写法,在Hierarchy Viewer上呈现出来的性能测试差别:


13) Optimizing Your Layout

下图举例演示了如何优化ListItem的布局,经过RelativeLayout替代旧方案中的嵌套LinearLayout来优化布局。


运算篇

1) Intro to Compute and Memory Problems

Android中的Java代码会须要通过编译优化再执行的过程。代码的不一样写法会影响到Java编译器的优化效率。例如for循环的不一样写法就会对编译器优化这段代码产生不一样的效率,当程序中包含大量这种可优化的代码的时候,运算性能就会出现问题。想要知道如何优化代码的运算性能就须要知道代码在硬件层的执行差别。

2) Slow Function Performance

若是你写了一段代码,它的执行效率比想象中的要差不少。咱们须要知道有哪些因素有可能影响到这段代码的执行效率。例如:比较两个float数值大小的执行时间是int数值的4倍左右。这是由于CPU的运算架构致使的,以下图所示:


虽然现代的CPU架构获得了很大的提高,也许并不存在上面所示的那么大的差别,可是这个例子说明了代码写法上的差别会对运算性能产生很大的影响。

一般来讲有两类运行效率差的状况:第1种是相对执行时间长的方法,咱们能够很轻松的找到这些方法并作必定的优化。第2种是执行时间短,可是执行频次很高的方法,由于执行次数多,累积效应下就会对性能产生很大的影响。

修复这些细节效率问题,须要使用Android SDK提供的工具,进行仔细的测量,而后再进行微调修复。

3) Traceview Walkthrough

经过Android Studio打开里面的Android Device Monitor,切换到DDMS窗口,点击左边栏上面想要跟踪的进程,再点击上面的Start Method Tracing的按钮,以下图所示:


启动跟踪以后,再操控app,作一些你想要跟踪的事件,例如滑动listview,点击某些视图进入另一个页面等等。操做完以后,回到Android Device Monitor,再次点击Method Tracing的按钮中止跟踪。此时工具会为刚才的操做生成TraceView的详细视图。


关于TraceView中详细数据如何查看,这里不展开了,有不少文章介绍过。

4) Batching and Caching

为了提高运算性能,这里介绍2个很是重要的技术,Batching与Caching。

Batching是在真正执行运算操做以前对数据进行批量预处理,例如你须要有这样一个方法,它的做用是查找某个值是否存在与于一堆数据中。假设一个前提,咱们会先对数据作排序,而后使用二分查找法来判断值是否存在。咱们先看第一种状况,下图中存在着屡次重复的排序操做。


在上面的那种写法下,若是数据的量级并不大的话,应该还能够接受,但是若是数据集很是大,就会有严重的效率问题。那么咱们看下改进的写法,把排序的操做打包绑定只执行一次:


上面就是Batching的一种示例:把重复的操做拎出来,打包只执行一次。

Caching的理念很容易理解,在不少方面都有体现,下面举一个for循环的例子:


上面这2种基础技巧很是实用,积极恰当的使用可以显著提高运算性能。

5) Blocking the UI Thread

提高代码的运算效率是改善性能的一方面,让代码执行在哪一个线程也一样很重要。咱们都知道Android的Main Thread也是UI Thread,它须要承担用户的触摸事件的反馈,界面视图的渲染等操做。这就意味着,咱们不能在Main Thread里面作任何非轻量级的操做,相似I/O操做会花费大量时间,这颇有可能会致使界面渲染发生丢帧的现象,甚至有可能致使ANR。防止这些问题的解决办法就是把那些可能有性能问题的代码移到非UI线程进行操做。

6) Container Performance

另一个咱们须要注意的运算性能问题是基础算法的合理选择,例如冒泡排序与快速排序的性能差别:


避免咱们重复造轮子,Java提供了不少现成的容器,例如Vector,ArrayList,LinkedList,HashMap等等,在Android里面还有新增长的SparseArray等,咱们须要了解这些基础容器的性能差别以及适用场景。这样才可以选择合适的容器,达到最佳的性能。


内存篇

1) Memory, GC, and Performance

众所周知,与C/C++须要经过手动编码来申请以及释放内存有所不一样,Java拥有GC的机制。Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不一样的内存数据类型分别执行不一样的GC操做。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象一般都是会快速被建立而且很快被销毁回收的,同时这个区域的GC操做速度也是比Old Generation区域的GC操做速度更快的。


除了速度差别以外,执行GC操做的时候,全部线程的任何操做都会须要暂停,等待GC操做完成以后,其余操做才可以继续运行。


一般来讲,单个的GC并不会占用太多时间,可是大量不停的GC操做则会显著占用帧间隔时间(16ms)。若是在帧间隔时间里面作了过多的GC操做,那么天然其余相似计算,渲染等操做的可用时间就变得少了。

2) Memory Monitor Walkthrough

Android Studio中的Memory Monitor能够很好地帮助咱们查看程序的内存使用状况。




3) Memory Leaks

内存泄漏表示的是再也不用到的对象由于被错误引用而没法进行回收。


发生内存泄漏会致使Memory Generation中的剩余可用Heap Size愈来愈小,这样会致使频繁触发GC,更进一步引发性能问题。

举例内存泄漏,下面init()方法来自某个自定义View:

private void init() {
ListenerCollector collector = new ListenerCollector();
collector.setListener(this, mListener);
}

上面的例子容易存在内存泄漏,若是activity由于设备翻转而从新建立,自定义的View会自动从新把新建立出来的mListener给绑定到ListenerCollector中,可是当activity被销毁的时候,mListener却没法被回收了。

4) Heap Viewer Walkthrough

下图演示了Android Tools里面的Heap Viewer的功能,咱们能够看到当前进程中的Heap Size的状况,分别有哪些类型的数据,占比是多少。


5) Understanding Memory Churn

Memory Churn内存抖动,内存抖动是由于在短期内大量的对象被建立又立刻被释放。瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,会触发GC从而致使刚产生的对象又很快被回收。即便每次分配的对象占用了不多的内存,可是他们叠加在一块儿会增长Heap的压力,从而触发更多其余类型的GC。这个操做有可能会影响到帧率,并使得用户感知到性能问题。


解决上面的问题有简洁直观方法,若是你在Memory Monitor里面查看到短期发生了屡次内存的涨跌,这意味着颇有可能发生了内存抖动。


同时咱们还能够经过Allocation Tracker来查看在短期内,同一个栈中不断进出的相同对象。这是内存抖动的典型信号之一。

当你大体定位问题以后,接下去的问题修复也就显得相对直接简单了。例如,你须要避免在for循环里面分配对象占用内存,须要尝试把对象的建立移到循环体以外,自定义View中的onDraw方法也须要引发注意,每次屏幕发生绘制以及动画执行过程当中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操做,避免建立对象。对于那些没法避免须要建立对象的状况,咱们能够kao虑对象池模型,经过对象池来解决频繁建立与销毁的问题,可是这里须要注意结束使用以后,须要手动释放对象池中的对象。

6) Allocation Tracker

关于Allocation Tracker工具的使用,不展开了,参kao下面的连接:

7) Improve Your Code To Reduce Churn

下面演示一个例子,如何经过修改代码来避免内存抖动。优化以前的内存检测图:


定位代码以后,修复了String拼接的问题:


优化以后的内存监测图:


8) Recap

上面提到了三种测量内存的工具,下面再简要归纳一下他们各自的特色:

  • Memory Monitor:跟踪整个app的内存变化状况。

  • Heap Viewer:查看当前内存快照,便于对比分析哪些对象有可能发生了泄漏。

  • Allocation Tracker:追踪内存对象的来源。


电量篇

1) Understanding Battery Drain

手机各个硬件模块的耗电量是不同的,有些模块很是耗电,而有些模块则相对显得耗电量小不少。


电量消耗的计算与统计是一件麻烦并且矛盾的事情,记录电量消耗自己也是一个费电量的事情。惟一可行的方案是使用第三方监测电量的设备,这样才可以获取到真实的电量消耗。

当设备处于待机状态时消耗的电量是极少的,以N5为例,打开飞行模式,能够待机接近1个月。但是点亮屏幕,硬件各个模块就须要开始工做,这会须要消耗不少电量。

使用WakeLock或者JobScheduler唤醒设备处理定时的任务以后,必定要及时让设备回到初始状态。每次唤醒蜂窝信号进行数据传递,都会消耗不少电量,它比WiFi等操做更加的耗电。


2) Battery Historian

Battery Historian是Android 5.0开始引入的新API。经过下面的指令,能够获得设备上的电量消耗信息:

$ adb shell dumpsys batterystats > xxx.txt  //获得整个设备的电量消耗信息
$ adb shell dumpsys batterystats > com.package.name > xxx.txt //获得指定app相关的电量消耗信息

获得了原始的电量消耗数据以后,咱们须要经过Google编写的一个python脚本把数据信息转换成可读性更好的html文件:

$ python historian.py xxx.txt > xxx.html

打开这个转换事后的html文件,能够看到相似TraceView生成的列表数据,这里的数据信息量很大,这里就不展开了。


3) Track Battery Status & Battery Manager

咱们能够经过下面的代码来获取手机的当前充电状态:

// 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!”);
}

在上面的例子演示了如何当即获取到手机的充电状态,获得充电状态信息以后,咱们能够有针对性的对部分代码作优化。好比咱们能够判断只有当前手机为AC充电状态时 才去执行一些很是耗电的操做。

/**
 * 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);
}

4) Wakelock and Battery Drain

高效的保留更多的电量与不断促使用户使用你的App会消耗电量,这是矛盾的选择题。不过咱们可使用一些更好的办法来平衡二者。

假设你的手机里面装了大量的社交类应用,即便手机处于待机状态,也会常常被这些应用唤醒用来检查同步新的数据信息。Android会不断关闭各类硬件来延长手机的待机时间,首先屏幕会逐渐变暗直相当闭,而后CPU进入睡眠,这一切操做都是为了节约宝贵的电量资源。可是即便在这种睡眠状态下,大多数应用仍是会尝试进行工做,他们将不断的唤醒手机。一个最简单的唤醒手机的方法是使用PowerManager.WakeLock的API来保持CPU工做并防止屏幕变暗关闭。这使得手机能够被唤醒,执行工做,而后回到睡眠状态。知道如何获取WakeLock是简单的,但是及时释放WakeLock也是很是重要的,不恰当的使用WakeLock会致使严重错误。例如网络请求的数据返回时间不肯定,致使原本只须要10s的事情一直等待了1个小时,这样会使得电量白白浪费了。这也是为什么使用带超时参数的wakelock.acquice()方法是很关键的。

可是仅仅设置超时并不足够解决问题,例如设置多长的超时比较合适?何时进行重试等等?解决上面的问题,正确的方式多是使用非精准定时器。一般状况下,咱们会设定一个时间进行某个操做,可是动态修改这个时间也许会更好。例如,若是有另一个程序须要比你设定的时间晚5分钟唤醒,最好可以等到那个时候,两个任务捆绑一块儿同时进行,这就是非精肯定时器的核心工做原理。咱们能够定制计划的任务,但是系统若是检测到一个更好的时间,它能够推迟你的任务,以节省电量消耗。


这正是JobScheduler API所作的事情。它会根据当前的状况与任务,组合出理想的唤醒时间,例如等到正在充电或者链接到WiFi的时候,或者集中任务一块儿执行。咱们能够经过这个API实现不少免费的调度算法。

5) Network and Battery Drain

下面内容来自官方Training文档中高效下载章节关于手机(Radio)蜂窝信号对电量消耗的介绍。

一般状况下,使用3G移动网络传输数据,电量的消耗有三种状态:

  • Full power: 能量最高的状态,移动网络链接被激活,容许设备以最大的传输速率进行操做。

  • Low power: 一种中间状态,对电量的消耗差很少是Full power状态下的50%。

  • Standby: 最低的状态,没有数据链接须要传输,电量消耗最少。

下图是一个典型的3G Radio State Machine的图示(来自AT&T,详情请点击这里):


总之,为了减小电量的消耗,在蜂窝移动网络下,最好作到批量执行网络请求,尽可能避免频繁的间隔网络请求。

经过前面学习到的Battery Historian咱们能够获得设备的电量消耗数据,若是数据中的移动蜂窝网络(Mobile Radio)电量消耗呈现下面的状况,间隔很小,又频繁断断续续的出现,说明电量消耗性能很很差:


通过优化以后,若是呈现下面的图示,说明电量消耗的性能是良好的:


另外WiFi链接下,网络传输的电量消耗要比移动网络少不少,应该尽可能减小移动网络下的数据传输,多在WiFi环境下传输数据。


那么如何才可以把任务缓存起来,作到批量化执行呢?下面就轮到Job Scheduler出场了。

6) Using Job Scheduler

使用Job Scheduler,应用须要作的事情就是判断哪些任务是不紧急的,能够交给Job Scheduler来处理,Job Scheduler集中处理收到的任务,选择合适的时间,合适的网络,再一块儿进行执行。

下面是使用Job Scheduler的一段简要示例,须要先有一个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);
        }
    }
}

而后模拟经过点击Button触发N个任务,交给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);
        }
    }
}

做者介绍:胡凯(@胡凯me),就任于腾讯,从事Android开发的工做,我的博客:http://hukai.me/

相关文章
相关标签/搜索