Service使用AlarmManager实现后台定时任务

在Android 4.0.3版本中, 使用AlarmManager实现后台定时任务是比较好的方案, 其实现机制, 是利用Service的 onStartCommand() 方法, 在每次被AlarmManager唤醒后, 执行任务并注册下一次唤醒到AlarmManager. 涉及的代码

1. 新建DaemonService, 实现 onStartCommand() 方法, 在这个方法中新开线程执行任务, 并再次将AlarmReceiver注册到AlarmManager. 注: 同样的注册多次调用时, 不会注册多个, 而是会进行更新. 这个方法会在Activity中调用 startService(intent); 方法时被调用.

    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
new DaemonThread().start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000; Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}

2. onDestroy() 方法在调用 Activity stopService(intent) 时会被调用, 此时需要将AlarmReceiver从AlarmManager中cancel掉.

    @Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.cancel(pi);
super.onDestroy();
}

3. 新建Receiver, 用来注册到AlarmManager, 用于将来响应Alarm消息. 在内部的onReceive方法中, 启动DaemonService

public class AlarmReceiver extends BroadcastReceiver {

    @Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, DaemonService.class);
context.startService(i);
}
}

4. 在AndroidManifest.xml中添加Service和Receiver

    <application
<!--... -->
<service android:name=".DaemonService" ></service>
<receiver android:name=".AlarmReceiver" ></receiver>
</application>

5. 在Activity中, 对应控件的点击响应中添加service的启动, 停止代码

    @Override
public boolean onOptionsItemSelected(MenuItem item) {
// ... if (id == R.id.action_start) {
Intent intent = new Intent(this, DaemonService.class);
startService(intent);
return true;
} if (id == R.id.action_stop) {
Intent intent = new Intent(this, DaemonService.class);
stopService(intent);
return true;
} // ... return super.onOptionsItemSelected(item);
}

定时任务中使用 AsyncTask和 httpUrlConnection访问网址, 使用Callback进行结果回调

1. 新建HttpAsyncCallback接口, 要接收AsyncTask返回数据的, 都要实现这个接口

public interface HttpAsyncCallback {
// This function will be called from inside of your AsyncTask when you are ready to callback to
// your controllers (like a fragment, for example) The object in the completionHandler will be
// whatever it is that you need to send to your controllers
void completionHandler(Boolean success, int type, Object object);
}

2. 新建HttpAsyncTask类, 进行实际的HTTP访问

public class HttpAsyncTask extends AsyncTask<String, Void, Void> {
public static final int METHOD_GET = 0;
public static final int METHOD_POST = 1; private static final String TAG = HttpAsyncTask.class.getSimpleName(); private String postData;
private int method;
private int connectTimeout;
private int readTimeout;
private String encoding; private int type;
private HttpAsyncCallback callback; public HttpAsyncTask(int method, String encoding) {
this(null, method, encoding, 10000, 10000, 0, null);
} public HttpAsyncTask(int method, String encoding, int type, HttpAsyncCallback callback) {
this(null, method, encoding, 10000, 10000, type, callback);
} public HttpAsyncTask(String postData, int method, String encoding, int type, HttpAsyncCallback callback) {
this(postData, method, encoding, 10000, 10000, type, callback);
} public HttpAsyncTask(String postData, int method, String encoding, int connectTimeout, int readTimeout, int type, HttpAsyncCallback callback) {
this.postData = postData;
this.method = method;
this.encoding = encoding;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout; this.type = type;
this.callback = callback;
} @Override
protected Void doInBackground(String... strings) {
Log.d(TAG, "Timestamp:" + System.currentTimeMillis());
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL(strings[0]).openConnection();
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
if (method == METHOD_GET) {
connection.setRequestMethod("GET");
} else {
// get请求的话默认就行了,post请求需要setDoOutput(true),这个默认是false的。
connection.setDoOutput(true);
connection.setRequestMethod("POST");
if (this.postData != null) {
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(postData);
writer.flush();
}
} int statusCode = connection.getResponseCode();
if (statusCode == 200) {
InputStream in = connection.getInputStream();
byte[] bytes = getBytesByInputStream(in);
String response = new String(bytes, encoding);
Log.d(TAG, response);
// From here you can convert the string to JSON with whatever JSON parser you like to use
// After converting the string to JSON, I call my custom callback. You can follow this
// process too, or you can implement the onPostExecute(Result) method
// Use the response to create the object you need
if (callback != null) {
callback.completionHandler(true, type, "Timestamp:" + System.currentTimeMillis() + ", " + response);
}
} else {
Log.d(TAG, statusCode+"");
if (callback != null) {
callback.completionHandler(false, type, statusCode);
}
}
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
} finally {
if (connection != null){
connection.disconnect();
}
}
return null;
} private byte[] getBytesByInputStream(InputStream is) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
try {
while ((length = is.read(buffer)) != -1) {
bos.write(buffer, 0, length);
}
} catch (IOException e) {
Log.e(TAG, e.getMessage());
} finally {
try {
bos.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
}
return bos.toByteArray();
} public static String formDataToString(Map<String, String> data, String encoding) {
StringBuilder sb = new StringBuilder();
String con = "";
for (String key : data.keySet()) {
String value = data.get(key);
try {
key = URLEncoder.encode(key, encoding);
value = URLEncoder.encode(value, encoding);
sb.append(con).append(key).append("=").append(value);
con = "&";
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "UnsupportedEncodingException " + encoding + " in processing:" + key);
}
}
return sb.toString();
} public static String formDataToJson(Map<String, String> data, String encoding) {
if (data != null) {
JSONObject jsonObject = new JSONObject(data);
return jsonObject.toString();
}
return null;
}
}

使用 Callback和BroadcastReceiver实现消息通信

1. 在DaemonService中实现HttpAsyncCallback接口, 用于接收HttpAsyncTask任务执行结果

public class DaemonService extends Service implements HttpAsyncCallback {
private static final String TAG = DaemonService.class.getSimpleName(); // ... @Override
public void completionHandler(Boolean success, int type, Object object) {
Log.d(TAG, "completionHandler");
}
}

2. 在DaemonService的onStartCommand()方法中, 将自己做为参数传给HttpAsyncTask. 1和2是为了将AsyncTask的结果传回Service

    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Map<String, String> map = new HashMap<>();
map.put("phone", "13800138000");
HttpAsyncTask task = new HttpAsyncTask(HttpAsyncTask.formDataToString(map, "UTF-8"), HttpAsyncTask.METHOD_POST, "UTF-8", 0, this);
task.execute("https://www.toutiao.com/api/pc/realtime_news/");
//new DaemonThread("").start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
long triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000; Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}

3. 为了从Service将结果传回Fragment, 需要在Fragment中注册一个BroadcastReceiver, 实现onReceive方法, 在这个方法中将结果更新到TextView, 在onCreateView中初始化这个receiver, 在onStart和onStop方法中进行注册和取消. 注意: 从fragment中获取TextView时, 需要在onActivityCreated方法中才行, 在其他的事件方法(onCreateView, onAttach中, findViewById拿到的是null

public class MainActivityFragment extends Fragment {
private static final String TAG = MainActivityFragment.class.getSimpleName();
private BroadcastReceiver receiver;
private TextView tv; ... @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String s = intent.getStringExtra("msg");
tv.setText(s);
}
};
return inflater.inflate(R.layout.fragment_main, container, false);
} @Override
public void onAttach(Context context) {
Log.d(TAG, "onAttach");
super.onAttach(context);
} @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
tv = getActivity().findViewById(R.id.sample_text);
} @Override
public void onStart() {
Log.d(TAG, "onStart");
super.onStart();
if (receiver != null) {
IntentFilter intentFilter = new IntentFilter(MainActivityFragment.class.getName() + ".TextView");
getActivity().registerReceiver(receiver, intentFilter);
}
} @Override
public void onStop() {
Log.d(TAG, "onStop");
if (receiver != null) {
getActivity().unregisterReceiver(receiver);
}
super.onStop();
}
}

代码在 https://github.com/MiltonLai/android-service-example

Android(4.0.3+): Service, AsyncTask, 定时任务和UI通信的更多相关文章

  1. Android中Activity、Service和线程之间的通信

    Activity.Service和线程应该是Android编程中最常见的几种类了,几乎大多数应用程序都会涉及到这几个类的编程,自然而然的,也就会涉及到三者之间的相互通信,本文就试图简单地介绍一下这三者 ...

  2. Android 7.0 中 ContentProvider 实现原理

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:汪毅雄 导语: 本文描述了ContentProvider发布者和调用者这两在Framework层是如何实现的. 作为Android的四大 ...

  3. Android 5.0 行为变更

    Android 5.0 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更.本文重点介绍您应该了解并在开发应用时加以考虑的一些主要变更. 如果您之前发布过 Android 应用,请注意 ...

  4. [Android分享] 如何解决Android 5.0中出现的警告:Service Intent must be explicit

    Android 5.0程序运行报Service Intent must be explicit错误,原因是5.0的service必须显式调用 改成 Intent intent = new Intent ...

  5. 如何解决Android 5.0中出现的警告:Service Intent must be explicit

    有些时候我们使用Service的时需要采用隐私启动的方式,但是Android 5.0一出来后,其中有个特性就是Service Intent  must be explitict,也就是说从Lollip ...

  6. 解决Android 5.0中出现的警告:Service Intent must be explicit

    extends:http://www.eoeandroid.com/thread-568853-1-1.html 本帖最后由 469874851 于 2015-3-11 18:15 编辑 有些时候我们 ...

  7. 我的Android进阶之旅------>如何解决Android 5.0中出现的警告: Service Intent must be explicit:

    我的Android进阶之旅-->如何解决Android 5.0中出现的警告: java.lang.IllegalArgumentException: Service Intent must be ...

  8. 我的Android进阶之旅------&gt;怎样解决Android 5.0中出现的警告: Service Intent must be explicit:

    我的Android进阶之旅-->怎样解决Android 5.0中出现的警告: java.lang.IllegalArgumentException: Service Intent must be ...

  9. Android 8.0 启动后台service 出错 IllegalStateException: Not allowed to start service Intent

    错误原因: Android 8.0 不再允许后台service直接通过startService方式去启动, 具体行为变更如下: 如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况 ...

随机推荐

  1. verilog语法实例学习(1)

    本文档中通过verilog实例来学习verilog语法.Verilog是一种硬件描述语言,它具有并发性和时序性.并发性是指不同硬件模块的同时操作,时序性是指信号的赋值或操作在时钟的边沿进行.由于作者本 ...

  2. bat 批处理切换到当前脚本所在文件夹

    bat 批处理切换到当前脚本所在文件夹   切换到当前脚本所在的文件夹 ? 1 cd  %~dp0 另外附上一些bat基本内容 —————————————————————————————— 批处理常用 ...

  3. 2.2 dubbo-spi源码解析

    package com.alibaba.dubbo.demo.test; import com.alibaba.dubbo.common.extension.ExtensionLoader; impo ...

  4. Remote Desktop Session中如何触发Ctrl+Alt+Delete?

    Ctrl+Alt+End is a keyboard shortcut used in a Remote Desktop Session to display the security dialog ...

  5. Spark(十二)--性能调优篇

    一段程序只能完成功能是没有用的,只能能够稳定.高效率地运行才是生成环境所需要的. 本篇记录了Spark各个角度的调优技巧,以备不时之需. 一.配置参数的方式和观察性能的方式 额...从最基本的开始讲, ...

  6. variable_scope和name_scope差别

    先看代码:   #命名空间函数tf.variable_scope()和tf.name_scope()函数区别于使用       import tensorflow as tf       with t ...

  7. 使用nginx反向代理到不同服务器(共享同一端口)配置文件

    使用nginx反向代理到不同服务器(共享同一端口)配置文件 https://blog.csdn.net/wang_k_123/article/details/72779443 https://www. ...

  8. OpenGL ES Shading Language中应尽量避免使用循环

      In addition to basic if-then-else statements, it is possible to write for, while, and do-while loo ...

  9. 基于PHP构建OAuth 2.0 服务端 认证平台

    OAuth2.0 认证服务 安装 你可以在github上下载OAuth Server PHP,也可以用下列命令下载,不过内容都是一样的 mkdir my-oauth2-walkthrough cd m ...

  10. mysql下载安装使用教程

    今天花了点时间终于把mysql安装好了,哈哈!下面上干货! (1)从官网下载mysql.地址https://www.mysql.com/ 图:按照图上的步骤就能找到 a.首页点击    DOWNLOA ...