1.手机电量消耗的基本认识
最耗电的应用一般有:屏幕保持唤醒状态、使用蜂窝式无线数据交换(3G4G),GPS定位等。
在打开屏幕,所有要使用CPU/GPU工作的动作都会唤醒屏幕,都会消耗电量。与应用程序唤醒设备的情况不同。
现在很多手机厂商在手机出厂时将监测电量的功能阉割掉了,因为记录电量消耗本身属于费电量的动作。。
现在一般使用第三方的检测电量的设备或者软件。
第三方的电量检测设备具有非常专业的从硬件板卡接入采集电量信号的电量监测设备,这样可以获得真实的电量消耗,第三方硬件监测的时候是用的自己的供电而不是用的手机的电量。
1.1 常见电量消耗的检测图
下图是使用专业的第三方电量接入的设备测试出的结果,是真实的电量消耗数据,没有消耗手机电量。
待机状态的电量消耗:
使用和唤醒屏幕后:非常明显的具有两个电量使用的尖峰。
CUP唤醒时的高峰线:
屏幕点亮之后的手机正常的电量消耗状态:
当设备短时间没有操作之后,设备会主动进行休眠,可以极大的减少手机的耗电量。
1.2 蜂窝式无线数据交换(3G4G)电量消耗示意图
从图中可以看到具有3个高峰点:
【第1个高峰点】当设备通过无线网发送数据的时候,为了使用蜂窝无线硬件启动的时候会出现唤醒的电量消耗高峰。
【第2个高峰点】发送数据包
【第3个高峰点】接受数据包会消耗大量电量
开启无线模式这个过程非常耗电,硬件为了防止频繁开启关闭耗电,采取会在短时间内保持开启模式,防止短时间内还有数据包需要接收。
但是使用Android L版本提供了新的电量测试工具来优化应用程序的耗电。如果测试Android其他版本的电量消耗情况时不需要考虑兼容性的问题。
2. 第三方工具Battery Historian
【注意】【第三方工具的作用】帮助分析文件内容,并非是获取数据的手段。
数据的来源是ADB的获取的,获取的文件形式是txt。第三方的工具的作用是对该文件进行分析,
因为该文件信息太多,太复杂。注意明白该工具的作用。
第三方工具Battery Historian,电量使用记录分析工具。
工作流程是通过ADB获取的数据,通过使用Battery Historian工具分析处理后,得到的html结果文件,用浏览器可以直接查看。
Battery Historian工具是一个独立的Python开源脚本,可以从gitbub上下载。
gitHub地址:https://github.com/google/battery-historian
改工具有两种使用方式:Docer和源码编译,在源码编译的环节需要搭建环境,比较麻烦,后面有讨论。
来自于官方的截图:https://raw.githubusercontent.com/google/battery-historian/master/screenshots/timeline.png
2.1 使用Docker方法
首先提示个坑:Docker只支持Windows10
Docker是一种容器,一般用于云计算和大数据平台。提倡的一种思想就是:软件即服务。
一条指令可以将其他人已经发布的docker服务环境一次全部copy过来(注意是整个软件环境哦,相当于复制了一台一模一样的主机,连软件都不要安装了,全有了)
类似于虚拟机的镜像。
Docker安装地址:https://docs.docker.com/engine/installation/
官方提供的执行下面的命令:
docker -- run -p <port>:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999
2.0版本的其他人的地址,2.0版本的。(镜像地址:blystad/battery-historian或者bhaavan/battery-historian)
临时开辟的程序服务
docker -- run -p 6666:9999 blystad/battery-historian --port 9999
单独系统服务更正规的服务
docker run -d -p 9999:9999 bhaavan/battery-historian
2.2 编译源码方法之环境搭建
搭建的环境有:①Go环境 ②Git 这是最熟悉不过的了 ③Python环境 ④Java环境
【1】【Go环境】
下载地址:
https://golang.org/doc/install
https://golang.org/doc/install?download=go1.7.3.windows-amd64.msi
国内地址:https://www.golangtc.com/download
https://www.golangtc.com/static/go/1.9.2/go1.9.2.windows-amd64.msi
一路下一步,然后配置Go环境
配置GOROOT和GOPATH
a. GOROOT的作用是告诉Go 命令和其他相关工具,在哪里去找到安装在你系统上的Go包,所以这里配置的是GO的安装目录
b.GOPATH可以简单理解为是工程的目录,所以创建一个GO的工程路径
C.最后配置一下环境变量,把Go的bin目录放到path环境变量中
检查Go是否安装成功,打开命令行输入Go version
【2】Python环境
下载地址:https://www.python.org/ 注意仅支持python2.7
https://www.python.org/ftp/python/2.7/python-2.7.amd64.msi
环境变量配置
添加Path的路径,是Python的安装路径
输入命令行 python –V(注意是大写V)检查是否安装成功
其他环境自行解决
【源码下载】
在gitBash中输入命令行:注意后面的三个点是英文的。
go get -d -u github.com/google/battery-historian/...
运行Battery Historian
$ cd $GOPATH/src/github.com/google/battery-historian
# Compile Javascript files using the Closure compiler $ go run setup.go
# Run Historian on your machine (make sure $PATH contains $GOBIN) $ go run cmd/battery-historian/battery-historian.go [--port <default:9999>]
等待数分钟或者10分钟左右,如果仍然没有下载成功,可以手动下载,如下操作
下载【closure-library】和【closure-compiler】和【flot-axislabels】,解压放到GOROOT目录下third_party文件夹
https://github.com/google/closure-library
上面的资源可能下载不成功:使用下面的地址:https://pan.baidu.com/s/1xOCUs7T9lkj_a_l3V3REoA
https://github.com/google/closure-compiler
https://github.com/markrcote/flot-axislabels
下方的closure-compiler和closure-library和flot-axislabels文件夹 ../battery-historian\third_party;
如果没有均手动创建
然后执行下面的命令,启动环境
$ go run cmd/battery-historian/battery-historian.go # Run Historian on your machine (make sure $PATH contains $GOBIN) $ go run cmd/battery-historian/battery-historian.go [--port <default:9999>]
【注意】
Remember, you must always run battery-historian from inside the $GOPATH/src/github.com/google/battery-historian directory: cd $GOPATH/src/github.com/google/battery-historian go run cmd/battery-historian/battery-historian.go [--port <default:9999>]
检查/battery-historian是否运行,登录网址 http://localhost:9999查看
2.3 编译源码方法之生成Bugreport日志文件
battery-historian工具需要使用bugreport中的Battery History内容
【1】先断开adb服务,然后开启adb服务
adb kill-server 这一步很重要,因为当我们开发时做电量记录时会打开很多可能造成冲突的东西。为了保险起见我们重启adb。
adb kill-server
adb devices就会自动连接查找手机。当然也可以adb start-server
adb devices
adb start-server
【2】 重置电池数据收集
数据,我们在开始的时候需要通过以下命令来打开电池数据的获取以及重置:
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
上面的操作相当于初始化操作,如果不这么做会有一大堆的干扰的数据,看起来会比较痛苦。
然后把数据线直接拔掉(防止数据线造成充放电数据干扰),现在做一些测试,手动或者跑一些自动化的case都行。
经过一段时间后,重新连接手机确认adb连上了,运行下面这条命令来将bugreport的信息保存到txt文档中,
adb bugreport > bugreport.txt
或者用下面的命令也可以:
adb shell dumpsys batterystats > batterystats.txt
adb shell dumpsys batterystats > com.example.android.sunshine.app > batterystats.txt
加上包名可以限制输出的数据是我们要检测的。
【注意】上面通过两种方式生成的文件不一样,一个是bugreport.txt,一个是 batterystats.txt。前者的内容更丰富,包含了除了电量意外的其他信息。后者只包含了电量信息。
但是:(重要!!!!):后者是不可以通过Docker服务器来分析的,只能通过html浏览器的方式查看分析。
但是这个txt的数据可读性不强。接下来我们就要用到这个battery-historian工具了
2.4 编译源码方法之生成分析文件
有两种方式分析这个文件:(historian-V1之前的版本 和historian-V2最新的版本)
2.4.1 historian-V1之前的版本分析方式
将txt文档转化为html文件,命令如下。
python historian.py -a bugreport.txt > battery.html
python historian.py -a batterystats.txt > batterystats.html
上面的historian.py脚本是Python写的,所以需要python环境,然后从github上下载这个脚本。文件在github上面的scripts目录下面,需要下载到命令行所在的目录
下载地址:https://github.com/google/battery-historian/blob/master/scripts/historian.py
上面两条命令执行成功后,会在目录下发现两个文件
bugreport.txt和battery.html,这个时候我们用google浏览器打开html文件,可以看到如下信息:
【bug解决查看】https://github.com/google/battery-historian/issues
【bug1】
注意:浏览器显示满屏的文字+英文,无法生成图形报告,或者打开生成的HTML显示错误如下,
WARNING: Visualizer disabled. If you see this message, download the HTML then open it。
解决:需要使用vpn翻墙访问谷歌服务。
【bug2】
Battery history not present in bugreport.
【bug3】
注意: json: unsupported value: NaN 描述:the problem started when reset the battery stats and enabled full-wake-history。 解决:重启手机再试。
【bug4】打开生成的HTML显示错误如下:
WARNING: Visualizer disabled. If you see this message, download the HTML then open it.
解决:需要翻墙访问谷歌服务。
【bug5】进部署好的动脑服务器docker,显示错误如下:
{"UploadResponse":[{"sdkVersion":23,"historianV2Cs...
https://github.com/google/battery-historian/issues/64
解决:You need a network connection.需要翻墙。
【bug6】进部署好的服务器docker,没有显示错误,但是最上面提示了红色的颜色块,表示访问出错。
解决:无法翻墙访问谷歌服务导致。
【各个参数的意义】
在bugreport.txt找到Battery History数据栏类似下面的信息:
------------------------------------------------------------------------------- DUMP OF SERVICE batterystats: Battery History (2% used, 5980 used of 256KB, 45 strings using 2592): 0 (9) RESET:TIME: 2015-03-05-15-21-56 0 (2) 100 c0900422 status=discharging health=good plug=none temp=200 volt=4167 +running +wake_lock +sensor +screen data_conn=edge phone_signal_strength=great brightness=medium proc=u0a15:"android.process.acore" 0 (2) 100 c0900422 proc=u0a7:"com.android.cellbroadcastreceiver" 0 (2) 100 c0900422 proc=u0a53:"com.android.gallery3d"
在html中信息都能从bugreport.txt中找到相应的信息
【横坐标】
上面的10,20代表的就是秒的意思,它是以一分钟为周期,到第60秒的时候变为0。横坐标就是一个时间范围,咱们的例子中统计的数据是以重置为起点,获取bugreport内容时刻为终点。
一共采集了多长时间的数据,图表下也有信息说明。(经其他人的反馈,这个坐标间隔是会随着时间的长度发生改变,所以要以你的实际情况为准。这个缩放级别可以调整的,如下图:)
【列表中的各个参数的意义】
【重点关注的参数】job、wake-lock、coon、WiFi、mobile_ratio(蜂窝信号)
【对于得到的结果进行分析】如果出现连续掉电的情况就是可疑点,查看对应的纵列对应的应用名称。
【plugged】
充电状态,这一栏显示是否进行了充电,以及充电的时间范围。例如上图反映了在下午3:57插入了数据线,然后一直持续了数据采集结束。
【screen】
屏幕是否点亮,这一点可以考虑到睡眠状态和点亮状态下电量的使用信息。
【top】
该栏显示当前时刻哪个app处于最上层,就是当前手机运行的app,用来判断某个app对手机电量的影响,这样也能判断出该app的耗电量信息。该栏记录了应用在某一个时刻启动,以及运行的时间,这对我们比对不同应用对性能的影响有很大的帮助。
【Sync】
是否跟后台同步.
可以把鼠标停在某一项上面。可以看到何时sync同步 启动的,持续时间Duration多久。
电池容量不会显示单一行为消耗的具体电量,这里只能显示使用电池的频率和时长,可以看分时段的剩余电量来了解具体消耗了多少电量。
【wake_lock】
cpu、gpu的很多的组件在记录在某一个时刻具体哪些部件开始工作。
wake_lock 该属性是记录wake_lock模块的工作时间。是否有停止的时候等。下图看到的是alarm的工作的时间段。
在实际的使用:可以打开项目开发的app,通过下面列出的需要监测的可以的点可以知道在监听的时间内app调用了哪些cpu、gpu的资源。
【running】
界面的状态,主要判断是否处于idle的状态。用来判断无操作状态下电量的消耗。
【Job】
后台的工作,比如服务service的运行。从下面图中可以看到qihoo的AppStore和鲁大师都在运行后台服务。
其他的列表如下:
序号 | 名称 | 解释 |
1 | wake_lock_in |
wake_lock有不同的组件,这个地方记录在某一个时刻,有哪些部件开始工作,以及工作的时间。 |
2 | gps |
gps是否开启 |
3 | phone_in_call |
是否进行通话 |
4 | data_conn |
数据连接方式的改变,上面的edge是说明采用的gprs的方式连接网络的。此数据可以看出手机是使用2g,3g,4g还是wifi进行数据交换的。这一栏可以看出不同的连接方式对电量使用的影响。 |
5 | status |
电池状态信息,有充电,放电,未充电,已充满,未知等不同状态。 这一栏记录了电池状态的改变信息。 |
6 | phone_signal_strength |
手机信号状态的改变。 这一栏记录手机信号的强弱变化图,依次来判断手机信号对电量的影响。 |
7 | health |
电池健康状态的信息,这个信息一定程度上反映了这块电池使用了多长时间。 |
8 | plug |
充电方式,usb或者插座,以及显示连接的时间。 |
各个参数的意义: CPU runing: cpu运行的状态 Kernel only uptime: 只有kernell运行? Userspace wakelock: 用户空间申请的锁 Screen: 屏幕是否点亮 Top app: 当前在内存中的应用,按内存占用率排序 Activity Manager Proc: 活跃的用户进程 Crashes(logcat): 某个时间点出现crash的应用 Doze: 是否进入doze模式 Device active: 和Doze相反 JobScheduler: 异步作业调度 SyncManager: 同步操作 Temp White List: 电量优化白名单 Phone call: 是否打电话 GPS: 是否使用GPS Network connectivity: 网络连接状态(wifi、mobile是否连接) Mobile signal strength: 移动信号强度(great\good\moderate\poor) Wifi scan: 是否在扫描wifi信号 Wifi supplicant: 是否有wifi请求 Wifi radio: 是否正在通过wifi传输数据 Wifi signal strength: wifi信号强度 Wifi running: wifi组件是否在工作(未传输数据) Wifi on: 同上 Audio: 音频子系统? Camera: 相机是否在工作 Video:是否在播放视频 Foreground process: 前台进程 Package install: 是否在进行包安装 Package active: 包管理在工作 Battery level: 电池当前电量 Temperature: 电池温度 Plugged: 连接usb或者充电 Charging on: 在充电 Logcat misc: 是否在导出日志
2.5 historian-V2最新的版本的方式
将生成bugreport.txt文件在 http://localhost:9999 中上传文件生成报告(前提在本地或者某服务器上搭好了battery-historian项目环境)
其实在这里也可以看到两种版本分析模式:
3.从充电方式入手优化
【电池充电方式的获取】
1 private boolean checkForPower(){ 2 //获取电池的充电状态 3 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 4 Intent intent = this.registerReceiver(null,filter); 5 6 //BatteryManager 7 int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1); 8 boolean usb = chargePlug ==BatteryManager.BATTERY_PLUGGED_USB;//usb充电 9 boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;//交流电 10 //无线充电---API>=17 11 boolean wireless = false; 12 if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1) { 13 wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS; 14 } 15 return (usb||ac||wireless); 16 }
为了省电,有些工作可以放当手机插上电源的时候去做。往往这样的情况非常多。
像这些不需要及时地和用户交互的操作可以放到后面处理。
比如:360手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人等到云端。
提问:拍照和图片的处理,他们可以做一些电量的优化吗?
假如现在没有充电,电量比较低,拍照动作是需要立马执行的,
但是图片处理(需要消耗大量的计算---电量的大量消耗)是否可以放在用户手机插上电源之后来处理?
如何立即获取手机当前充电状态,我们可以有针对性地对一些代码做优化。
4. 唤醒cpu优化
唤醒cpu的使用场合:在工作需要非常消耗cpu资源时候,需要cpu全力配合任务的时候,例如在后台服务运行时,可能cpu是休眠的,需要首先唤醒,需要cpu的配合大量的工作。
系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。
有任务需要唤醒CPU高效执行的时候,就会给CPU加wake_lock锁。
经常犯的错误,很容易去唤醒CPU来干货,但是很容易忘记释放wake_lock.
1 public class MainActivity extends AppCompatActivity { 2 TextView wakelock_text ; 3 PowerManager pw; 4 PowerManager.WakeLock mWakelock; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 11 wakelock_text = (TextView)findViewById(R.id.wakelock_text); 12 pw = (PowerManager) getSystemService(POWER_SERVICE); //获取系统电源管理服务 13 mWakelock = pw.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"mywakelock"); //获取系统的WakeLock 14 15 } 16 public void execut(View view){ 17 wakelock_text.setText("正在下载....");19 for(int i=0;i<10;i++){ 20 mWakelock.acquire();//唤醒CPU 21 wakelock_text.append("连接中……"); 22 // wakelock_text.append(""); 23 //下载 24 if(isNetWorkConnected()) { 25 new SimpleDownloadTask().execute(); 26 }else{ 27 wakelock_text.append("没有网络连接。"); 28 } 29 } 30 } 31 32 private boolean isNetWorkConnected() { //判断网络是否连接 33 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 34 NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); 35 return (activeNetworkInfo!=null&&activeNetworkInfo.isConnected()); 36 } 37 38 /** 39 * Uses AsyncTask to create a task away from the main UI thread. This task creates a 40 * HTTPUrlConnection, and then downloads the contents of the webpage as an InputStream. 41 * The InputStream is then converted to a String, which is displayed in the UI by the 42 * onPostExecute() method. 43 */ 44 private static final String LOG_TAG = "ricky"; 45 private class SimpleDownloadTask extends AsyncTask<Void, Void, String> { 46 47 @Override 48 protected String doInBackground(Void... params) { 49 try { 50 // Only display the first 50 characters of the retrieved web page content. 51 int len = 50; 52 53 URL url = new URL("https://www.baidu.com"); 54 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 55 conn.setReadTimeout(10000); // 10 seconds 56 conn.setConnectTimeout(15000); // 15 seconds 57 conn.setRequestMethod("GET"); 58 //Starts the query 59 conn.connect(); 60 int response = conn.getResponseCode(); 61 Log.d(LOG_TAG, "The response is: " + response); 62 InputStream is = conn.getInputStream(); 63 64 // Convert the input stream to a string 65 Reader reader = new InputStreamReader(is, "UTF-8"); 66 char[] buffer = new char[len]; 67 reader.read(buffer); 68 return new String(buffer); 69 70 } catch (IOException e) { 71 return "Unable to retrieve web page."; 72 } 73 } 74 75 @Override 76 protected void onPostExecute(String result) { 77 wakelock_text.append("\n" + result + "\n"); 78 releaseWakeLock(); //释放锁 79 } 80 } 81 82 private void releaseWakeLock(){ 83 if(mWakelock.isHeld()){ 84 mWakelock.release();//记得释放CPU锁 85 wakelock_text.append("释放锁!"); 86 } 87 } 88 89 90 }
解决:powerManager的API 记得添加权限: <uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> mWakelock.acquire();//唤醒CPU mWakelock.release();//记得释放CPU锁
5.JobScheduler的使用
5.1 省电实例源码
1 public class MyJobService extends JobService { 2 private static final String LOG_TAG = "MyJobService"; 3 4 @Override 5 public void onCreate() { 6 super.onCreate(); 7 Log.i(LOG_TAG, "MyJobService created"); 8 } 9 10 @Override 11 public void onDestroy() { 12 super.onDestroy(); 13 Log.i(LOG_TAG, "MyJobService destroyed"); 14 } 15 16 @Override 17 public boolean onStartJob(JobParameters params) { 18 // This is where you would implement all of the logic for your job. Note that this runs 19 // on the main thread, so you will want to use a separate thread for asynchronous work 20 // (as we demonstrate below to establish a network connection). 21 // If you use a separate thread, return true to indicate that you need a "reschedule" to 22 // return to the job at some point in the future to finish processing the work. Otherwise, 23 // return false when finished. 24 Log.i(LOG_TAG, "Totally and completely working on job " + params.getJobId()); 25 // First, check the network, and then attempt to connect. 26 if (isNetworkConnected()) { 27 new SimpleDownloadTask() .execute(params); 28 return true; 29 } else { 30 Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face"); 31 } 32 return false; 33 } 34 35 @Override 36 public boolean onStopJob(JobParameters params) { 37 // Called if the job must be stopped before jobFinished() has been called. This may 38 // happen if the requirements are no longer being met, such as the user no longer 39 // connecting to WiFi, or the device no longer being idle. Use this callback to resolve 40 // anything that may cause your application to misbehave from the job being halted. 41 // Return true if the job should be rescheduled based on the retry criteria specified 42 // when the job was created or return false to drop the job. Regardless of the value 43 // returned, your job must stop executing. 44 Log.i(LOG_TAG, "Whelp, something changed, so I'm calling it on job " + params.getJobId()); 45 return false; 46 } 47 48 /** 49 * Determines if the device is currently online. 50 */ 51 private boolean isNetworkConnected() { 52 ConnectivityManager connectivityManager = 53 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 54 NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 55 return (networkInfo != null && networkInfo.isConnected()); 56 } 57 58 /** 59 * Uses AsyncTask to create a task away from the main UI thread. This task creates a 60 * HTTPUrlConnection, and then downloads the contents of the webpage as an InputStream. 61 * The InputStream is then converted to a String, which is logged by the 62 * onPostExecute() method. 63 */ 64 private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> { 65 66 protected JobParameters mJobParam; 67 68 @Override 69 protected String doInBackground(JobParameters... params) { 70 // cache system provided job requirements 71 mJobParam = params[0]; 72 try { 73 InputStream is = null; 74 // Only display the first 50 characters of the retrieved web page content. 75 int len = 50; 76 77 URL url = new URL("https://www.google.com"); 78 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 79 conn.setReadTimeout(10000); //10sec 80 conn.setConnectTimeout(15000); //15sec 81 conn.setRequestMethod("GET"); 82 //Starts the query 83 conn.connect(); 84 int response = conn.getResponseCode(); 85 Log.d(LOG_TAG, "The response is: " + response); 86 is = conn.getInputStream(); 87 88 // Convert the input stream to a string 89 Reader reader = null; 90 reader = new InputStreamReader(is, "UTF-8"); 91 char[] buffer = new char[len]; 92 reader.read(buffer); 93 return new String(buffer); 94 95 } catch (IOException e) { 96 return "Unable to retrieve web page."; 97 } 98 } 99 100 @Override 101 protected void onPostExecute(String result) { 102 jobFinished(mJobParam, false); 103 Log.i(LOG_TAG, result); 104 } 105 } 106 }
【调用】
1 public class MainActivity extends AppCompatActivity { 2 TextView wakelock_text; 3 PowerManager pw; 4 PowerManager.WakeLock mWakelock; 5 private ComponentName serviceComponent; 6 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity_main); 11 12 wakelock_text = (TextView) findViewById(R.id.wakelock_text); 13 pw = (PowerManager) getSystemService(POWER_SERVICE); 14 mWakelock = pw.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mywakelock"); 15 serviceComponent = new ComponentName(this,MyJobService.class); 16 } 17 18 public void execut(View view) { 19 wakelock_text.setText("正在下载...."); 20 //优化 21 JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 22 for (int i = 0; i < 500; i++) { 23 JobInfo jobInfo = new JobInfo.Builder(i,serviceComponent) 24 .setMinimumLatency(5000)//5秒 最小延时、 25 .setOverrideDeadline(60000)//maximum最多执行时间 26 // .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免费的网络---wifi 蓝牙 USB 27 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意网络--- 28 /** 29 设置重试/退避策略,当一个任务调度失败的时候执行什么样的测量采取重试。 30 initialBackoffMillis:第一次尝试重试的等待时间间隔ms 31 *backoffPolicy:对应的退避策略。比如等待的间隔呈指数增长。 32 */ 33 // .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) 34 .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR) 35 // .setPeriodic (long intervalMillis)//设置执行周期,每隔一段时间间隔任务最多可以执行一次。 36 // .setPeriodic(long intervalMillis,long flexMillis)//在周期执行的末端有一个flexMiliis长度的窗口期,任务就可以在这个窗口期执行。 37 //设置设备重启后,这个任务是否还要保留。需要权限:RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x 38 // .setPersisted(boolean isPersisted); 39 // .setRequiresCharging(boolean )//是否需要充电 40 // .setRequiresDeviceIdle(boolean)//是否需要等设备出于空闲状态的时候 41 // .addTriggerContentUri(uri)//监听uri对应的数据发生改变,就会触发任务的执行。 42 // .setTriggerContentMaxDelay(long duration)//设置Content发生变化一直到任务被执行中间的最大延迟时间 43 //设置Content发生变化一直到任务被执行中间的延迟。如果在这个延迟时间内content发生了改变,延迟时间会重写计算。 44 // .setTriggerContentUpdateDelay(long durationMilimms) 45 .build(); 46 jobScheduler.schedule(jobInfo); 47 } 48 }
5.2 JobScheduler使用
【文章部分内容转载自】https://blog.csdn.net/aroundme/article/details/55214203
【谷歌官方地址】https://github.com/googlesamples/android-JobScheduler
官方demo实例效果
通过设置各项的参数,然后触发执行动作。
【注意】需要声明权限
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.oztaking.www.jobschedulerapplication"> 4 <!--声明权限--> 5 <uses-permission android:name="android.permission.INTERNET" /> 6 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 7 8 <application 9 android:allowBackup="true" 10 android:icon="@mipmap/ic_launcher" 11 android:label="jobschedulerapplication" 12 android:roundIcon="@mipmap/ic_launcher_round" 13 android:supportsRtl="true" 14 android:theme="@style/AppTheme"> 15 <activity android:name=".MainActivity"> 16 <intent-filter> 17 <action android:name="android.intent.action.MAIN"/> 18 19 <category android:name="android.intent.category.LAUNCHER"/> 20 </intent-filter> 21 </activity> 22 <!--声明服务及权限--> 23 <service android:name=".MyJobService" 24 android:permission="android.permission.BIND_JOB_SERVICE" 25 android:exported="true"/> 26 </application> 27 28 </manifest>
【源码】【MyJobService.java】
1 import static com.oztaking.www.jobschedulerapplication.MainActivity.MESSENGER_INTENT_KEY; 2 import static com.oztaking.www.jobschedulerapplication.MainActivity.MSG_COLOR_START; 3 import static com.oztaking.www.jobschedulerapplication.MainActivity.MSG_COLOR_STOP; 4 import static com.oztaking.www.jobschedulerapplication.MainActivity.WORK_DURATION_KEY; 5 15 public class MyJobService extends JobService { 16 17 private static final String TAG = MyJobService.class.getSimpleName(); 18 private Messenger mActivityMessenger; 19 @Override 20 public void onCreate() { 21 super.onCreate(); 22 Log.i(TAG, "Service created"); 23 } 24 @Override 25 public void onDestroy() { 26 super.onDestroy(); 27 Log.i(TAG, "Service destroyed"); 28 } 29 /** 30 * When the app's MainActivity is created, it starts this service. This is so that the 31 * activity and this service can communicate back and forth. See "setUiCallback()" 32 * 33 * 当应用程序的MainActivity被创建时,它启动这个服务。 34 * 这是为了使活动和此服务可以来回通信。 请参见“setUiCallback()” 35 */ 36 @Override 37 public int onStartCommand(Intent intent, int flags, int startId) { 38 mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY); 39 return START_NOT_STICKY; 40 } 41 @Override 42 public boolean onStartJob(final JobParameters params) { 43 // The work that this service "does" is simply wait for a certain duration and finish 44 // the job (on another thread). 45 // 该服务做的工作只是等待一定的持续时间并完成作业(在另一个线程上)。 46 sendMessage(MSG_COLOR_START, params.getJobId()); 47 long duration = params.getExtras().getLong(WORK_DURATION_KEY); 48 // Uses a handler to delay the execution of jobFinished(). 49 // 使用一个handler处理程序来延迟jobFinished()的执行。 50 Handler handler = new Handler(); 51 handler.postDelayed(new Runnable() { 52 @Override 53 public void run() { 54 sendMessage(MSG_COLOR_STOP, params.getJobId()); 55 jobFinished(params, false); 56 } 57 }, duration); 58 Log.i(TAG, "on start job: " + params.getJobId()); 59 // Return true as there's more work to be done with this job. 60 // 返回true,很多工作都会执行这个地方 61 return true; 62 } 63 @Override 64 public boolean onStopJob(JobParameters params) { 65 // Stop tracking these job parameters, as we've 'finished' executing. 66 // 停止跟踪这些作业参数,因为我们已经完成工作。 67 sendMessage(MSG_COLOR_STOP, params.getJobId()); 68 Log.i(TAG, "on stop job: " + params.getJobId()); 69 // Return false to drop the job. 70 // 返回false来销毁这个工作 71 return false; 72 } 73 private void sendMessage(int messageID, @Nullable Object params) { 74 // If this service is launched by the JobScheduler, there's no callback Messenger. It 75 // only exists when the MainActivity calls startService() with the callback in the Intent. 76 // 如果此服务由JobScheduler启动,则没有回调Messenger。 77 // 它仅在MainActivity在Intent中使用回调函数调用startService()时存在。 78 if (mActivityMessenger == null) { 79 Log.d(TAG, "Service is bound, not started. There's no callback to send a message to."); 80 return; 81 } 82 Message m = Message.obtain(); 83 m.what = messageID; 84 m.obj = params; 85 try { 86 mActivityMessenger.send(m); 87 } catch (RemoteException e) { 88 Log.e(TAG, "Error passing service object back to activity."); 89 } 90 } 91 92 }
【MainAcitivity源码】
1 /** 2 * Schedules and configures jobs to be executed by a {@link JobScheduler}. 3 * <p> 4 * {@link MyJobService} can send messages to this via a {@link Messenger} 5 * that is sent in the Intent that starts the Service. 6 * 7 * 8 * 计划和配置要由{@link JobScheduler}执行的作业。 9 * 10 * {@link MyJobService}可以通过{@link Messenger}向其发送消息 11 * 在启动服务的Intent中发送。 12 */ 13 public class MainActivity extends Activity { 14 private static final String TAG = MainActivity.class.getSimpleName(); 15 //消息 16 public static final int MSG_UNCOLOR_START = 0; 17 public static final int MSG_UNCOLOR_STOP = 1; 18 public static final int MSG_COLOR_START = 2; 19 public static final int MSG_COLOR_STOP = 3; 20 public static final String MESSENGER_INTENT_KEY 21 = BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY"; 22 public static final String WORK_DURATION_KEY = 23 BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY"; 24 private EditText mDelayEditText; 25 private EditText mDeadlineEditText; 26 private EditText mDurationTimeEditText; 27 private RadioButton mWiFiConnectivityRadioButton; 28 private RadioButton mAnyConnectivityRadioButton; 29 30 private CheckBox mRequiresChargingCheckBox; 31 private CheckBox mRequiresIdleCheckbox; 32 private ComponentName mServiceComponent; 33 private int mJobId = 0; 34 // Handler for incoming messages from the service. 35 // 用于来自服务的传入消息的处理程序。 36 private IncomingMessageHandler mHandler; 37 @Override 38 public void onCreate(Bundle savedInstanceState) { 39 super.onCreate(savedInstanceState); 40 setContentView(R.layout.sample_main); 41 // Set up UI. 42 // 设置UI 43 mDelayEditText = (EditText) findViewById(R.id.delay_time); 44 mDurationTimeEditText = (EditText) findViewById(R.id.duration_time); 45 mDeadlineEditText = (EditText) findViewById(R.id.deadline_time); 46 mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered); 47 mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any); 48 mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging); 49 mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle); 50 mServiceComponent = new ComponentName(this, MyJobService.class); 51 mHandler = new IncomingMessageHandler(this); 52 } 53 @Override 54 protected void onStop() { 55 // A service can be "started" and/or "bound". In this case, it's "started" by this Activity 56 // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call 57 // to stopService() won't prevent scheduled jobs to be processed. However, failing 58 // to call stopService() would keep it alive indefinitely. 59 // 服务可以是“开始”和/或“绑定”。 在这种情况下,它由此Activity“启动” 60 // 和“绑定”到JobScheduler(也被JobScheduler称为“Scheduled”)。 61 // 对stopService()的调用不会阻止处理预定作业。 62 // 然而,调用stopService()失败将使它一直存活。 63 stopService(new Intent(this, MyJobService.class)); 64 super.onStop(); 65 } 66 @Override 67 protected void onStart() { 68 super.onStart(); 69 // Start service and provide it a way to communicate with this class. 70 // 启动服务并提供一种与此类通信的方法。 71 Intent startServiceIntent = new Intent(this, MyJobService.class); 72 Messenger messengerIncoming = new Messenger(mHandler); 73 startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming); 74 startService(startServiceIntent); 75 } 76 /** 77 * Executed when user clicks on SCHEDULE JOB. 78 * 79 * 当用户单击SCHEDULE JOB时执行。 80 */ 81 public void scheduleJob(View v) { 82 //开始配置JobInfo 83 JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent); 84 //设置任务的延迟执行时间(单位是毫秒) 85 String delay = mDelayEditText.getText().toString(); 86 if (!TextUtils.isEmpty(delay)) { 87 builder.setMinimumLatency(Long.valueOf(delay) * 1000); 88 } 89 //设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。 90 String deadline = mDeadlineEditText.getText().toString(); 91 if (!TextUtils.isEmpty(deadline)) { 92 builder.setOverrideDeadline(Long.valueOf(deadline) * 1000); 93 } 94 boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked(); 95 boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked(); 96 //让你这个任务只有在满足指定的网络条件时才会被执行 97 if (requiresUnmetered) { 98 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 99 } else if (requiresAnyConnectivity) { 100 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 101 } 102 //你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务。 103 builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked()); 104 //告诉你的应用,只有当设备在充电时这个任务才会被执行。 105 builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked()); 106 // Extras, work duration. 107 PersistableBundle extras = new PersistableBundle(); 108 String workDuration = mDurationTimeEditText.getText().toString(); 109 if (TextUtils.isEmpty(workDuration)) { 110 workDuration = "1"; 111 } 112 extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000); 113 builder.setExtras(extras); 114 // Schedule job 115 Log.d(TAG, "Scheduling job"); 116 JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 117 tm.schedule(builder.build()); 118 //tm.schedule(builder.build())会返回一个int类型的数据 119 //如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。 120 } 121 /** 122 * Executed when user clicks on CANCEL ALL. 123 * 124 * 当用户点击取消所有时执行 125 */ 126 public void cancelAllJobs(View v) { 127 JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 128 tm.cancelAll(); 129 Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show(); 130 } 131 /** 132 * Executed when user clicks on FINISH LAST TASK. 133 * 134 * 当用户点击取消上次任务时执行 135 */ 136 public void finishJob(View v) { 137 JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 138 List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs(); 139 if (allPendingJobs.size() > 0) { 140 // Finish the last one 141 int jobId = allPendingJobs.get(0).getId(); 142 jobScheduler.cancel(jobId); 143 Toast.makeText( 144 MainActivity.this, String.format(getString(R.string.cancelled_job), jobId), 145 Toast.LENGTH_SHORT).show(); 146 } else { 147 Toast.makeText( 148 MainActivity.this, getString(R.string.no_jobs_to_cancel), 149 Toast.LENGTH_SHORT).show(); 150 } 151 } 152 153 /** 154 * A {@link Handler} allows you to send messages associated with a thread. A {@link Messenger} 155 * uses this handler to communicate from {@link MyJobService}. It's also used to make 156 * the start and stop views blink for a short period of time. 157 * 158 * 159 * {@link Handler}允许您发送与线程相关联的消息。 160 * {@link Messenger}使用此处理程序从{@link MyJobService}进行通信。 161 * 它也用于使开始和停止视图在短时间内闪烁。 162 */ 163 private static class IncomingMessageHandler extends Handler { 164 // Prevent possible leaks with a weak reference. 165 // 使用弱引用防止内存泄露 166 private WeakReference<MainActivity> mActivity; 167 IncomingMessageHandler(MainActivity activity) { 168 super(/* default looper */); 169 this.mActivity = new WeakReference<>(activity); 170 } 171 @Override 172 public void handleMessage(Message msg) { 173 MainActivity mainActivity = mActivity.get(); 174 if (mainActivity == null) { 175 // Activity is no longer available, exit. 176 // 活动不再可用,退出。 177 return; 178 } 179 View showStartView = mainActivity.findViewById(R.id.onstart_textview); 180 View showStopView = mainActivity.findViewById(R.id.onstop_textview); 181 Message m; 182 switch (msg.what) { 183 /* 184 * Receives callback from the service when a job has landed 185 * on the app. Turns on indicator and sends a message to turn it off after 186 * a second. 187 * 188 * 当作业登录到应用程序时,从服务接收回调。 打开指示灯(上方View闪烁)并发送一条消息,在一秒钟后将其关闭。 189 */ 190 case MSG_COLOR_START: 191 // Start received, turn on the indicator and show text. 192 // 开始接收,打开指示灯(上方View闪烁)并显示文字。 193 showStartView.setBackgroundColor(getColor(R.color.start_received)); 194 updateParamsTextView(msg.obj, "started"); 195 // Send message to turn it off after a second. 196 // 发送消息,一秒钟后关闭它。 197 m = Message.obtain(this, MSG_UNCOLOR_START); 198 sendMessageDelayed(m, 1000L); 199 break; 200 /* 201 * Receives callback from the service when a job that previously landed on the 202 * app must stop executing. Turns on indicator and sends a message to turn it 203 * off after two seconds. 204 * 205 * 当先前执行在应用程序中的作业必须停止执行时, 206 * 从服务接收回调。 打开指示灯并发送一条消息, 207 * 在两秒钟后将其关闭。 208 * 209 */ 210 case MSG_COLOR_STOP: 211 // Stop received, turn on the indicator and show text. 212 // 停止接收,打开指示灯并显示文本。 213 showStopView.setBackgroundColor(getColor(R.color.stop_received)); 214 updateParamsTextView(msg.obj, "stopped"); 215 // Send message to turn it off after a second. 216 // 发送消息,一秒钟后关闭它。 217 m = obtainMessage(MSG_UNCOLOR_STOP); 218 sendMessageDelayed(m, 2000L); 219 break; 220 case MSG_UNCOLOR_START: 221 showStartView.setBackgroundColor(getColor(R.color.none_received)); 222 updateParamsTextView(null, ""); 223 break; 224 case MSG_UNCOLOR_STOP: 225 showStopView.setBackgroundColor(getColor(R.color.none_received)); 226 updateParamsTextView(null, ""); 227 break; 228 } 229 } 230 /** 231 * 更新UI显示 232 * @param jobId jobId 233 * @param action 消息 234 */ 235 private void updateParamsTextView(@Nullable Object jobId, String action) { 236 TextView paramsTextView = (TextView) mActivity.get().findViewById(R.id.task_params); 237 if (jobId == null) { 238 paramsTextView.setText(""); 239 return; 240 } 241 String jobIdText = String.valueOf(jobId); 242 paramsTextView.setText(String.format("Job ID %s %s", jobIdText, action)); 243 } 244 private int getColor(@ColorRes int color) { 245 return mActivity.get().getResources().getColor(color); 246 } 247 } 248 }
【使用场景】
应用场景
当你需要在Android设备满足某种条件才需要去执行处理数据,例如
- 应用具有您可以推迟的非面向用户的工作(定期数据库数据更新)
- 应用具有当插入设备时您希望优先执行的工作(充电时才希望执行的工作备份数据)
- 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据)、网盘在具有wifi网络的时候才进行资源的上传、视频资源的下载是在wifi的时候才下载
- 希望作为一个批次定期运行的许多任务
而使用JobScheduler可以很高效的完成这些情况。
所以相比于其他方式,JobScheduler的好处是显而易见的。
* 避免频繁的唤醒硬件模块,造成不必要的电量消耗。
* 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量;
JobScheduler和Android 6.0出现的Doze都一样,总结来说就是限制应用频繁唤醒硬件,从而达到省电的效果。
【使用方法】
1.创建JobService
首先需要自定义一个类来继承JobService,但是因为JobScheduler是要运行在API>21版本之上,需要做一些配置。
1.1. 在build.gradle中设置minSdkVersion为21,5.0以下都不支持
1.2. 使用注解不同版本采取不同策略,保证兼容?????没找到
重写onStartJob(JobParameters params) 和onStopJob(JobParameters params)方法。
1 public class JobSchedulerService extends JobService { 2 @Override 3 public boolean onStartJob(JobParameters params) { 4 return false; 5 } 6 @Override 7 public boolean onStopJob(JobParameters params) { 8 return false; 9 } 10 }
这个类必须实现两个方法,分别是onStartJob和 onStopJob
【onStartJob】
当任务开始时会执行onStartJob方法,因为这是系统用来触发已经被执行的任务。
这个方法返回一个boolean值。如果返回值是false,系统假设这个方法返回时任务已经执行完毕。
如果返回值是true,那么系统假定这个任务正要被执行,书写需要完成的任务,
当任务执行完毕时需要调用jobFinished来通知系统。
1 @Override 2 public boolean onStartJob(final JobParameters params) { 3 // The work that this service "does" is simply wait for a certain duration and finish 4 // the job (on another thread). 5 // 该服务做的工作只是等待一定的持续时间并完成作业(在另一个线程上)。 6 sendMessage(MSG_COLOR_START, params.getJobId()); 7 long duration = params.getExtras().getLong(WORK_DURATION_KEY); 8 // Uses a handler to delay the execution of jobFinished(). 9 // 使用一个handler处理程序来延迟jobFinished()的执行。 10 Handler handler = new Handler(); 11 handler.postDelayed(new Runnable() { 12 @Override 13 public void run() { 14 sendMessage(MSG_COLOR_STOP, params.getJobId()); 15 jobFinished(params, false); 16 } 17 }, duration); 18 Log.i(TAG, "on start job: " + params.getJobId()); 19 // Return true as there's more work to be done with this job. 20 // 返回true,很多工作都会执行这个地方 21 return true; 22 }
【onStopJob】
当系统接收到一个取消请求时,系统会调用onStopJob方法取消正在等待执行的任务。
其实onStopJob在jobFinished正常调用结束一个job时,是不会调用的,
只有在该job没有被执行完,就被cancel掉的时候会被回调到,比如某个job还没有执行就被JobScheduler给Cancel掉时,
或者在某个运行条件不满足时,比如原来在Wifi环境允许的某个任务,执行过程中切换到了非Wifi场景,那也会调用该方法。
改方法也返回一个boolean值,返回true表示会重新放到JobScheduler里reScheduler,false表示直接忽略。
1 @Override 2 public boolean onStopJob(JobParameters params) { 3 // Stop tracking these job parameters, as we've 'finished' executing. 4 // 停止跟踪这些作业参数,因为我们已经完成工作。 5 sendMessage(MSG_COLOR_STOP, params.getJobId()); 6 Log.i(TAG, "on stop job: " + params.getJobId()); 7 // Return false to drop the job. 8 // 返回false来销毁这个工作 9 return false; 10 }
=======================================================补充内容=开始======================================================
【补充内容】
启动一个service有两种方式,context.startService() and context.bindService(),下面就分别介绍一下。
1. Start service.
1 private void startSimpleService() { 2 Log.d(TAG, "startSimpleService ****"); 3 Intent service = new Intent(this, MySimpleService.class); 4 this.startService(service); 5 }
调用startService后会做些什么呢?首先如果service还没有create,系统会调用service的onCreate(),然后onStartCommand(),这个时候service处于foreground状态,
具有比较高的优先级,不会被系统kill掉。在2.0之前的版本里会用到onStart()这个api,但是之后的版本里这个api就被弃用了,因为android是系统来管理内存的,
在系统内存紧张的时候会根据优先级kill掉一些优先级比较低的进程,而service在start后就不再是foreground状态,优先级就会降低,这个时候就容易被系统kill掉,
而在2.0之前的版本,service在被系统kill掉之后,在系统内存不紧张的时候被restart,这个时候service的onCreate被调用,但是onStart并不会被调用,
因为没有startService,这个时候就会比较尴尬,service create了,但是却没事做,也不知道什么时候stop。因此在2.0之后的版本里使用了onStartCommand()
1 @Override 2 public int onStartCommand(Intent intent, int flags, int startId) { 4 Log.d(TAG, "onStartCommand ====="); 5 return Service.START_STICKY; 6 }
不同的地方是,它可以return一个int值,START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT.
START_STICKY: 和原来的onStart类似,在被系统kill掉后会restart,但是不同的是onStartCommand会被调用并传入一个null值作为intent,这个时候service就可以对这种case做出处理。
START_NOT_STICKY: 被kill掉后就不会自动restart了。
START_REDELIVER_INTENT: 如果service被kill掉了,系统会把之前的intent再次发送给service,直到service处里完成。
还有一点就是setforeground()这样的api也被弃用了,foreground状态是具有比较高的优先级的,有些开发者为了避免自己的service由于优先级太低被系统kill掉,
就一直让自己的service跑在foreground状态,这显然是不合适的,这样系统的自动回收机制就被废掉了,大量的service占用了资源却不能被kill掉。
在后来的版本里foreground的要有一个notification通知User,有一个service在foreground状态。
最后,start的service必需在不需要的时候主动stop掉,避免占用太多资源。
=======================================================补充内容=结束======================================================
2.Activity中配置JobInfo
JobInfo是控制任务执行的方式,包括时间,延时,状态选择等。
具体API:https://developer.android.google.cn/reference/android/app/job/JobInfo.html
想创建定时任务时,可以使用JobInfo.Builder来构建一个JobInfo对象,然后传递给的Service。
JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名。
1 //开始配置JobInfo 2 JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
接下来是一系列的设置:
1 JobInfo.Builder builder = new JobInfo.Builder(i, mServiceComponent) 2 .setMinimumLatency(2000) // 设置任务允许最少延迟时间 3 .setOverrideDeadline(50000) // 设置deadline,若到期还没有到达规定的条件也会执行 4 .setRequireNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) //设置网络条件,非蜂窝数据的 5 .setRequiresCharging(true) // 设置充电条件 6 .setRequiresDeviceIdle(false) // 设置手机是否idle状态 7 .build();
- setMinimumLatency(long minLatencyMillis):
这个函数能让设置任务的延迟执行时间(单位是毫秒),这个函数与setPeriodic(longtime)方法不兼容,如果这两个方法同时调用了就会引起异常
- setOverrideDeadline(long maxExecutionDelayMillis):
设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,任务也会被启动。与setMinimumLatency(long time)一样,这个方法也会与setPeriodic(long time),同时调用这两个方法会引发异常。
- setPersisted(boolean isPersisted)
告诉系统当设备重启之后任务是否还要继续执行。
- setRequiredNetworkType(int networkType):
任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。
另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。
另一种是JobInfo.NETWORK_TYPE_UNMETERED,表示设备不是蜂窝网络( 比如在WIFI连接时)时任务才会被执行。
- setRequiresCharging(boolean requiresCharging)
通知应用,只有当设备在充电时这个任务才会被执行。
- setRequiresDeviceIdle(boolean requiresDeviceIdle)
任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务
需要注意的是setRequiredNetworkType, setRequiresCharging 和setRequiresDeviceIdle这几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得你的任务在不满足条件的
情况下也会被执行。
3.开启MyJobService
在官方Demo中,在onStart()方法中开启了Intent注册了这个JobScheduler。
tm.schedule(builder.build())会返回一个int类型的数据
如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回在JobInfo.Builder中定义的标识id。
1 JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 2 tm.schedule(builder.build());
可以如下判断:
1 mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 2 xxxxxxx 3 if(mJobScheduler.schedule(builder.build()) <= 0 ) { 4 // if something goes wrong 5 }
4.任务取消
如果应用想停止某个任务,可以调用JobScheduler对象的cancel(int jobId)来实现;
如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()来实现。
1 /** 2 * Executed when user clicks on FINISH LAST TASK. 3 * 4 * 当用户点击取消上次任务时执行 5 */ 6 public void finishJob(View v) { 7 JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 8 List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs(); //获取任务的列表 9 if (allPendingJobs.size() > 0) { 10 // Finish the last one 11 int jobId = allPendingJobs.get(0).getId(); 12 jobScheduler.cancel(jobId); 13 Toast.makeText( 14 MainActivity.this, String.format(getString(R.string.cancelled_job), jobId), 15 Toast.LENGTH_SHORT).show(); 16 } else { 17 Toast.makeText( 18 MainActivity.this, getString(R.string.no_jobs_to_cancel), 19 Toast.LENGTH_SHORT).show(); 20 } 21 }
参考文章:
【1】http://www.cnblogs.com/ouyanliu/tag/app%20%E8%80%97%E7%94%B5%E4%BC%98%E5%8C%96/
【2】https://blog.csdn.net/aroundme/article/details/55214203
【3】https://www.jianshu.com/p/1d4ebae39263
6. JobScheduler 初探
【备注】未完待续
首先明白的是JobScheduler开启的是系统的服务,存在JobSchedulerService,属于是系统的进程。
但本应用同样也属于进程,两者之间通过进行了通信,属于跨进程访问,需要进程间的通讯。
在服务中看到了Binder的影子。
其中包含3个对象:
JobInfo、JobService、JobScheduler
其中JobInfo包含了JobService对象,最后通过JobScheduler进行调用JobInfo。
1 private ComponentName mServiceComponent; 2 3 mServiceComponent = new ComponentName(this, MyJobService.class); 4 5 //开始配置JobInfo 6 JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
1 //tm.schedule(builder.build())会返回一个int类型的数据 2 //如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。 3 JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 4 tm.schedule(builder.build());
【IJobScheduler.aidl】frameworks\base\core\java\android\app\job
1 package android.app.job; 2 3 import android.app.job.JobInfo; 4 5 /** 6 * IPC interface that supports the app-facing {@link #JobScheduler} api. 7 * {@hide} 8 */ 9 interface IJobScheduler { 10 int schedule(in JobInfo job); 11 void cancel(int jobId); 12 void cancelAll(); 13 List<JobInfo> getAllPendingJobs(); 14 }
7.网络优化
7.1 请求缓存
Http请求是可以做缓存的。专门的HttpResponseCache类。默认是关闭的。
官网api:https://developer.android.google.cn/reference/android/net/http/HttpResponseCache
【api1】install
public static HttpResponseCache install (File directory, long maxSize)
【参数1】【directory】保存缓存的目录
【参数2】【maxSize】缓存的大小,单位是bytes
【返回值】【HttpResponseCache】缓存的内容
官网给出的实例如下:指定了10MB大小的缓存
1 protected void onCreate(Bundle savedInstanceState) { 2 ... 3 4 try { 5 File httpCacheDir = new File(context.getCacheDir(), "http"); 6 long httpCacheSize = 10 * 1024 * 1024; // 10 MiB 7 HttpResponseCache.install(httpCacheDir, httpCacheSize); 8 } catch (IOException e) { 9 Log.i(TAG, "HTTP response cache installation failed:" + e); 10 } 11 } 12 13 protected void onStop() { 14 ... 15 16 HttpResponseCache cache = HttpResponseCache.getInstalled(); 17 if (cache != null) { 18 cache.flush(); 19 } 20 }
【api2】getInstalled
public static HttpResponseCache getInstalled ()
返回当前建立的缓存,如果没有缓存或者缓存的内容不是HttpResponseCache则返回null
【api3】delete
public void delete ()
卸载缓存并删除已经缓存的内容
1 public void deleteCache(View v){ 2 HttpResponseCache cache = HttpResponseCache.getInstalled(); 3 if(cache!=null){ 4 try { 5 cache.delete(); 6 Log.d(TAG, "清空缓存"); 7 } catch (IOException e) { 8 e.printStackTrace(); 9 } 10 } 11 }
7.2 简单示例
1 public void openCache(View v){ 2 try { 3 //Android系统默认的HttpResponseCache(网络请求响应缓存)是关闭的 4 //这样开启,开启缓存之后会在cache目录下面创建http的文件夹,HttpResponseCache会缓存所有的返回信息 5 File cacheDir = new File(getCacheDir(), "http");//缓存目录 6 long maxSize = 10*1024*1024;//缓存大小,单位byte 7 HttpResponseCache.install(cacheDir, maxSize ); 8 Log.d(TAG, "打开缓存"); 9 } catch (IOException e) { 10 // TODO Auto-generated catch block 11 e.printStackTrace(); 12 } 13 }
服务器源码:传输了简单的json数据,并设置了服务器请求的时间。
1 package com.dongnaoedu.servlet; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import com.google.gson.Gson; 12 import com.google.gson.JsonObject; 13 14 /** 15 * Servlet implementation class MyServlet1 16 */ 17 public class MyServlet1 extends HttpServlet { 18 private static final long serialVersionUID = 1L; 19 20 /** 21 * @see HttpServlet#HttpServlet() 22 */ 23 public MyServlet1() { 24 super(); 25 // TODO Auto-generated constructor stub 26 } 27 28 /** 29 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 30 */ 31 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 32 //给客户端响应一个json字符串 33 PrintWriter writer = response.getWriter(); 34 Gson gson = new Gson(); 35 JsonObject json = new JsonObject(); 36 json.addProperty("isValid", true); 37 json.addProperty("description", "information"); 38 writer.write(gson.toJson(json)); 39 //标识五秒之内不会再请求服务器 40 response.addHeader("Cache-control", "max-age=5"); 41 System.out.println("响应时间:"+System.currentTimeMillis()); 42 } 43 44 /** 45 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) 46 */ 47 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 48 doGet(request, response); 49 50 } 51 52 }
【设备端源码】
1 import java.io.BufferedReader; 2 import java.io.File; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.InputStreamReader; 6 import java.net.HttpURLConnection; 7 import java.net.URL; 8 9 import org.apache.http.HttpStatus; 10 11 import android.app.Activity; 12 import android.graphics.BitmapFactory; 13 import android.net.http.HttpResponseCache; 14 import android.os.Bundle; 15 import android.util.Log; 16 import android.view.View; 17 18 public class MainActivity extends Activity { 19 20 protected static final String TAG = "ricky"; 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 } 27 28 public void openCache(View v){ 29 try { 30 //Android系统默认的HttpResponseCache(网络请求响应缓存)是关闭的 31 //这样开启,开启缓存之后会在cache目录下面创建http的文件夹,HttpResponseCache会缓存所有的返回信息 32 File cacheDir = new File(getCacheDir(), "http");//缓存目录 33 long maxSize = 10*1024*1024;//缓存大小,单位byte 34 HttpResponseCache.install(cacheDir, maxSize ); 35 Log.d(TAG, "打开缓存"); 36 } catch (IOException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 41 } 42 43 public void request(View v){ 44 Log.d(TAG, "~~~~~~~~~~~~~"); 45 new Thread(new Runnable() { 46 47 @Override 48 public void run() { 49 try { 50 //10.0.2.2 51 HttpURLConnection conn = (HttpURLConnection) new URL("http://192.168.1.115:8080/dn_network_cache_server/MyServlet1").openConnection(); 52 conn.setRequestMethod("GET"); 53 conn.setDoInput(true); 54 conn.connect(); 55 int responseCode = conn.getResponseCode(); 56 if(responseCode==HttpStatus.SC_OK){ 57 InputStream is = conn.getInputStream(); 58 BufferedReader br = new BufferedReader(new InputStreamReader(is)); 59 Log.d(TAG, br.readLine()); 60 }else{ 61 Log.d(TAG, responseCode+""); 62 } 63 64 } catch (Exception e) { 65 e.printStackTrace(); 66 Log.d(TAG, "请求翻车了!!"); 67 } 68 } 69 }).start(); 70 } 71 72 public void request2(View v){ 73 new Thread(new Runnable() { 74 @Override 75 public void run() { 76 try { 77 BitmapFactory.decodeStream((InputStream) new URL("http://192.168.1.115:8080/dn_network_cache_server/icon.png").getContent()); 78 } catch (Exception e) { 79 e.printStackTrace(); 80 } 81 } 82 }).start(); 83 } 84 85 public void deleteCache(View v){ 86 HttpResponseCache cache = HttpResponseCache.getInstalled(); 87 if(cache!=null){ 88 try { 89 cache.delete(); 90 Log.d(TAG, "清空缓存"); 91 } catch (IOException e) { 92 e.printStackTrace(); 93 } 94 } 95 } 96 }
如果是图片可以导出到桌面然后修改后缀名可以打开展现图片