Android(4.0.3+): Service, AsyncTask, 定时任务和UI通信
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