Android中后台服务与通知的协作工作
2017-07-26 22:47
453 查看
概述
现在的APP应用基本上都会有在后台运行服务,比如音乐播放器,即便你离开了当前应用它也会继续播放;QQ在你离开当前页面打开另一个应用时,一旦有新消息,它也会及时地通知给你。它们是怎么实现的呢?Http网络连接
感谢干活集中营,为我们提供资源以及福利。我将利用其提供的API加载妹纸图片。让我们在忙碌的生活和学习中荡起双桨~应用将在RecyclerView视图(借助内置的GridLayoutManager)中显示内容。
layout/fragment_three.xml:
<android.support.v7.widget.RecyclerView android:id="@+id/is_reading_recycler" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView>
接下来在Fragment中实例化生成新建布局,并RecyclerView视图。
FragmentThree.java:
public class FragmentThree extends Fragment { private RecyclerView mPhotoRecyclerView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_three,container, false); mPhotoRecyclerView =(RecyclerView)v.findViewById(R.id.recycler); mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2)); return v; } }
在应用中,我们需要一个网络连接专用类。新建一个类用于对网络操作的封装。
FlickrFetchr.java:
private static final String TAG = "FlickrFetchr"; private Gson gson; /** * 获得网络数据(Json数据) * @param urlSpec * @return * @throws IOException */ public byte[] getUrlBytes(String urlSpec) throws IOException{ URL url = new URL(urlSpec); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); InputStream in = connection.getInputStream(); if(connection.getResponseCode()!=HttpURLConnection.HTTP_OK){ throw new IOException(connection.getResponseMessage()+": with"+ urlSpec); } int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead=in.read(buffer))>0){ out.write(buffer,0,bytesRead); } out.close(); return out.toByteArray(); } finally { connection.disconnect(); } } public String getUrlString(String urlSpec)throws IOException{ return new String(getUrlBytes(urlSpec)); } /** * 轮询网络地址获得网页数据 * @return */ public List<MeiZi.ResultsBean> fetchRecentPhotos() { String url = "http://gank.io/api/data/%E7%A6%8F%E5%88%A9/20/1"; return downloadGalleryItems(url); } public List<MeiZi.ResultsBean> searchPhotos(String query) { String url = "http://gank.io/api/data/"+query+"/20/1"; return downloadGalleryItems(url); } /** * 获得解析Json之后的List集合 * @param url * @return */ public List<MeiZi.ResultsBean> downloadGalleryItems(String url) { List<MeiZi.ResultsBean> items = new ArrayList<>(); try { String jsonString = getUrlString(url); parseItems(items, jsonString); Log.i(TAG, "Received JSON: " + jsonString); } catch (IOException ioe) { Log.e(TAG, "Failed to fetch items", ioe); } catch (JSONException e) { Log.e(TAG, "Failed to parse JSON", e); } return items; } /** * 解析Json数据 * @param items * @param data * @throws IOException * @throws JSONException */ private void parseItems(List<MeiZi.ResultsBean> items, String data) throws IOException,JSONException{ try { Gson gson = new Gson(); MeiZi meizi = gson.fromJson(data,MeiZi.class); items.addAll(meizi.getResults()); } catch (Exception e) { e.printStackTrace(); } }
这个类里面的方法应该大家都还是熟悉的吧?网络加载用的是HttpURLConnection,并没有用到Volley、OkHttp以及最近很火的Retrofit框架。
创建了URL并打开网络连接之后,我们便可循环调用read()方法读取网络数据,直到取完
为止。只要还有数据, InputStream类就会不断地输出字节流数据。数据全部取回后,关闭网络
连接,并将读取的数据写入ByteArrayOutputStream字节数组中。
虽然最重要的数据获取任务要靠getUrlBytes(String)方法完成, 但getUrlString(String)
才是我们需要的方法。它负责将getUrlBytes(String)方法获取的字节数据转换为String。
获取网络使用权限
AndroidManifest.xml<uses-permission android:name="android.permission.INTERNET" />
使用 AsyncTask 在后台线程上运行代码
使用后台线程最简便的方式是使用AsyncTask工具类。 AsyncTask创建后台线程后,我们便可在该线程上调用doInBackground(…)方法运行代码。
在FragmentThree.java中,添加一个名为FetchItemsTask的内部类。覆盖AsyncTask.
doInBackground(…)方法,从目标网站获取数据并记录日志,
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); setRetainInstance(true); new FetchItemsTask().execute(); } private class FetchItemsTask extends AsyncTask<Void,Void,List<MeiZi.ResultsBean>>{ @Override protected List<MeiZi.ResultsBean> doInBackground(Void... params) { final String query = null; // Just for testing if (query == null) { return new FlickrFetchr().fetchRecentPhotos(); } else { return new FlickrFetchr().searchPhotos(query); } } @Override protected void onPostExecute(List<MeiZi.ResultsBean> resultsBeen) { mItems = resultsBeen; setupAdapter(); } } }
关于AsyncTask的用法这里不再多讲,我们在后台线程中解析完数据加入到List集合中去,然后将数据源集合填充到界面上就可以舔屏啦!
创建模型对象类
MeiZhi.javapublic class MeiZi { /** * error : false * results : [{"_id":"59754e41421aa97de5c7c99d","createdAt":"2017-07-24T09:32:49.583Z","desc":"7-24","publishedAt":"2017-07-24T12:13:11.280Z","source":"chrome","type":"福利","url":"https://ws1.sinaimg.cn/large/610dc034gy1fhupzs0awwj20u00u0tcf.jpg","used":true,"who":"daimajia"},{"_id":"5971760e421aa90ca209c4af","createdAt":"2017-07-21T11:33:34.104Z","desc":"7-21","publishedAt":"2017-07-21T12:39:43.370Z","source":"chrome","type":"福利","url":"http://ww1.sinaimg.cn/large/610dc034ly1fhrcmgo6p0j20u00u00uu.jpg","used":true,"who":"daimajia"}] */ private boolean error; private List<ResultsBean> results; public boolean isError() { return error; } public void setError(boolean error) { this.error = error; } public List<ResultsBean> getResults() { return results; } public void setResults(List<ResultsBean> results) { this.results = results; } public static class ResultsBean { /** * _id : 59754e41421aa97de5c7c99d * createdAt : 2017-07-24T09:32:49.583Z * desc : 7-24 * publishedAt : 2017-07-24T12:13:11.280Z * source : chrome * type : 福利 * url : https://ws1.sinaimg.cn/large/610dc034gy1fhupzs0awwj20u00u0tcf.jpg * used : true * who : daimajia */ private String _id; private String createdAt; private String desc; private String publishedAt; private String source; private String type; private String url; private boolean used; private String who; public String get_id() { return _id; } public void set_id(String _id) { this._id = _id; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public String getPublishedAt() { return publishedAt; } public void setPublishedAt(String publishedAt) { this.publishedAt = publishedAt; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public boolean isUsed() { return used; } public void setUsed(boolean used) { this.used = used; } public String getWho() { return who; } public void setWho(String who) { this.who = who; } } }
我们其实只需要使用GsonFormat这个插件就可以自动为我们生成Bean类。
从 AsyncTask 回到主线程
我们回到视图层部分,实现在FragmentThree类的RecyclerView中显示图片标题。首先定义一个ViewHolder内部类。
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... class PhotoHolder extends RecyclerView.ViewHolder{ private ImageView mItemImageView; public PhotoHolder(View itemView) { super(itemView); mItemImageView = (ImageView)itemView.findViewById(R.id.fragment_photo_gallery_image_view); } public void bindGalleryItem(MeiZi.ResultsBean item) { Glide.with(itemView.getContext()) .load(item.getUrl()) .asBitmap() .placeholder(R.drawable.ic_action_photo) .into(mItemImageView); } } ... }
接 下 来 , 添 加 一 个 RecyclerView.Adapter 实 现 , 提 供 基 于Item 对 象 List 的
PhotoHolder。
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... private class PhotoAdapter extends RecyclerView.Adapter<PhotoAdapter.PhotoHolder> { private List<MeiZi.ResultsBean> mGalleryItems; public PhotoAdapter(List<MeiZi.ResultsBean> galleryItems) { mGalleryItems = galleryItems; } @Override public PhotoHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(getActivity()); View view = inflater.inflate(R.layout.fragment_three_item_layout, parent, false); return new PhotoHolder(view); } @Override public void onBindViewHolder(PhotoHolder holder, int position) { MeiZi.ResultsBean galleryItem = mGalleryItems.get(position); holder.bindGalleryItem(galleryItem); } @Override public int getItemCount() { return mGalleryItems.size(); } class PhotoHolder extends RecyclerView.ViewHolder{ ... } } ... }
既然RecyclerView要显示的数据已准备就绪,那么接下来编码完成adapter的配置和关联。实现setupAdapter()方法。
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; private RecyclerView mPhotoRecyclerView; private List<MeiZi.ResultsBean> mItems = new ArrayList<>(); ... @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { ... setupAdapter(); return v; private void setupAdapter() { if (isAdded()) { mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems)); } } ... }
注意,配置adapter前,应检查isAdded()的返回值是否为true。该检查确认fragment已与目
标activity相关联,进而保证getActivity()方法返回结果不为空。
启动应用程序,我们现在就可以看到福利啦!!那位同学把鼻血擦一擦。
后台服务
我们将为应用添加一项新功能,允许在后台下载新的网络数据。一旦干货集中营有了新的图片,用户就能在状态栏接收到通知消息。创建 IntentService
首先来创建服务。我们将使用IntentService。 IntentService并不是Android提供的唯一服务,但可能是最常用的。创建一个名为PullService的IntentService子类,它就是用来轮询搜索结果的服务。PullService.java :
public class PullService extends IntentService { private static final String TAG = "PollService"; public static Intent newIntent(Context context) { return new Intent(context, PollService.class); } public PullService() { super(TAG); } @Override protected void onHandleIntent(Intent intent) { Log.i(TAG, "Received an intent: " + intent); } }
IntentService的用法也不多讲,前面对它也有过介绍。
在manifest配置文件中添加服务。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bignerdranch.android.photogallery" > <uses-permission android:name="android.permission.INTERNET" /> <application ... > <activity android:name=".MainActivity" android:label="@string/app_name" > ... </activity> <service android:name=".PullService" /> </application> </manifest>
在FragmentThree中添加服务启动代码:
public class FragmentThreeextends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... @Override public void onCreate(Bundle savedInstanceState) { ... updateItems(); Intent i = PollService.newIntent(getActivity()); getActivity().startService(i); ... } ... }
运行应用,查看LogCat窗口,可看到类似以下的结果。
02-23 14:25:32.450 2692-2717/com.xxx.android.photogallery I/PullService: Received an intent: Intent { cmp=com.xxx.android.photogallery/.PullService }
安全的后台网络连接
服务将在后台轮询网站。为保证后台网络连接的安全性,我们需进一步完善代码。Android为用户提供了关闭后台应用网络连接的功能。对于非常耗电的应用而言,这项功能可极
大地改善手机的续航。
然而,这也意味着在后台连接网络时,需使用ConnectivityManager确认网络连接是否可用。
PullService.java :
public class PollService extends IntentService { private static final String TAG = "PollService"; ... @Override protected void onHandleIntent(Intent intent) { if (!isNetworkAvailableAndConnected()) { return; } Log.i(TAG, "Received an intent: " + intent); } private boolean isNetworkAvailableAndConnected() { ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); boolean isNetworkAvailable = cm.getActiveNetworkInfo() != null; boolean isNetworkConnected = isNetworkAvailable && cm.getActiveNetworkInfo().isConnected(); return isNetworkConnected; } }
检查网络是否可用的逻辑在isNetworkAvailableAndConnected()方法中。使用后台数据设
置选项关 闭后台数据 下载后,后 台服务也就 无法联网了。这样, ConnectivityManager.
getActiveNetworkInfo()就会返回null值;这就相当于告诉后台服务网络不可用,即使设备实际
是可以联网的。
如果后台服务能够使用网络,它会得到一个代表当前网络连接的android.net.NetworkInfo
实例。然后还要调用NetworkInfo.isConnected()方法检查当前网络是否已完全连接。
如果应用找不到可用网络,或者设备没有完全连上网, onHandleIntent(…)方法就会直接
返回(不会尝试去下载数据了,如果这个方法添加了数据下载代码的话)。这个做法是个好习惯:
网都连不上,还谈什么数据下载。
不 要 忘 了 , 要 使 用 getActiveNetworkInfo() 方 法 , 还 要 在 manifest 配 置 文 件 中 获 取
ACCESS_NETWORK_STATE权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bignerdranch.android.photogallery" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application ... > ... </application> </manifest>
查找最新返回结果
后 台 服 务 会 一 直 查 看 最 新 返 回 结 果 , 因 此 它 要 知 道 最 近 一 次 的 获 取 结 果 。 使 用SharedPreferences保存结果值再合适不过了。
创建QueryPreferences类以存储最近一次获取图片的ID。
QueryPreferences.java :
public class QueryPrefrences{ private static final String PREF_SEARCH_QUERY = "searchQuery"; private static final String PREF_LAST_RESULT_ID = "lastResultId"; private static final String PREF_IS_ALARM_ON = "isAlarmOn"; public static String getStoresQuery(Context context){ return PreferenceManager.getDefaultSharedPreferences(context) .getString(PREF_SEARCH_QUERY,null); } public static void setStoreQuery(Context context,String query){ PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(PREF_SEARCH_QUERY,query) .apply(); } /** * 获取上一次结果ID * @param context */ public static String getLastResultId(Context context){ return PreferenceManager.getDefaultSharedPreferences(context) .getString(PREF_LAST_RESULT_ID,null); } /** * 存储上一次结果ID * @param context * @param lastResultId */ public static void setLastResultId(Context context,String lastResultId){ PreferenceManager.getDefaultSharedPreferences(context) .edit() .putString(PREF_LAST_RESULT_ID,lastResultId) .apply(); } }
接下来就是完善服务代码了。以下为需要处理的任务:
从默认SharedPreferences中获取当前查询结果以及上一次结果ID;
使用FlickrFetchr类获取最新结果集;
如果有结果返回,抓取第一条结果;
确认是否不同于上一次结果ID;
将第一条结果存入SharedPreferences。
回到PullService.java中,添加以上任务的实现代码。
PullService.java :
public class PullService extends IntentService { private static final String TAG = "PullService"; ... @Override protected void onHandleIntent(Intent intent) { ... Log.i(TAG, "Received an intent: " + intent); String lastResultId = QueryPreferences.getLastResultId(this); List<GalleryItem> items; items = new FlickrFetchr().fetchRecentPhotos(); if (items.size() == 0) { return; } String resultId = items.get(0).getId(); if (resultId.equals(lastResultId)) { Log.i(TAG, "Got an old result: " + resultId); } else { Log.i(TAG, "Got a new result: " + resultId); } QueryPreferences.setLastResultId(this, resultId); } ... }
运行应用,可看到应用首先获取了最新结果。当然很可能并没有什么变化,因为网站上的数据并没有更新。
使用 AlarmManager 延迟运行服务
在没有activity运行的情况下,为在后台运行服务,得想个办法启动它。比如说,设置一个5分钟间隔的定时器。一种方式是调用Handler的sendMessageDelayed(…)或postDelayed(…)方法。但如果用户离开当前应用,进程就会停止, Handler消息也会随之消亡,因此该解决方案并不可靠。
Handler不行,我们还可以用AlarmManager。AlarmManager是可以发送Intent的系统服务。如何将要发送的intent告诉AlarmManager呢?使用PendingIntent。使用PendingIntent打包一个intent:“我想启动PullService服务。”然后,将其发送给系统中的其他部件,如AlarmManager。
在PullService类中,实现一个启停定时器的setServiceAlarm(Context,boolean)方法,该方法是个静态方法。这样,定时器代码和与之相关的代码就可以放在一起了,同时,其他系统部件还可以调用到它。要知道,定时器通常是从前端的fragment或其他控
制层代码中启停的。
PullService.java :
public class PullService extends IntentService { private static final String TAG = "PullService"; private static final int POLL_INTERVAL = 1000 * 60; // 60 seconds public static Intent newIntent(Context context) { return new Intent(context, PollService.class); } public static void setServiceAlarm(Context context, boolean isOn) { Intent i = PollService.newIntent(context); PendingIntent pi = PendingIntent.getService(context, 0, i, 0); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (isOn) { alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL, pi); } else { alarmManager.cancel(pi); pi.cancel(); } } ... }
以上代码中,首先是调用PendingIntent.getService(…)方法,创建一个用来启动PullService的PendingIntent。 PendingIntent.getService(…)方法打包了一个Context.startService(Intent)方法的调用。
它有四个参数:一个用来发送intent的Context,一个区分PendingIntent来源的请求代码,一个待发送的Intent对象以及一组用来决定如何创建PendingIntent的标志符。(稍后会使用其中的一个。)
接下来,需要设置或取消定时器。
FragmentThree.java :
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... @Override public void onCreate(Bundle savedInstanceState) { ... updateItems(); Intent i = PollService.newIntent(getActivity()); getActivity().startService(i); PollService.setServiceAlarm(getActivity(), true); ... } ... }
使用 PendingIntent 管理定时器
一个PendingIntent只能登记一个定时器。这也是isOn值为false时, setServiceAlarm(Context, boolean)方法的工作原理:首先调用AlarmManager.cancel(PendingIntent)方法撤销PendingIntent的定时器,然后撤销PendingIntent。
既然撤销定时器也随即撤消了PendingIntent,可通过检查PendingIntent是否存在来确认定 时 器 激 活 与 否 。
具 体 代 码 实 现 时 , 传 入 PendingIntent.FLAG_NO_CREATE 标 志 给PendingIntent.getService(…)方法即可。该标志表示如果PendingIntent不存在,则返回null,而不是创建它。
添加一个名为isServiceAlarmOn(Context)的新方法,并传入PendingIntent.FLAG_NO_CREATE标志,以判断定时器的启停状态。
PullService .java:
public class PullService extends IntentService { ... public static void setServiceAlarm(Context context, boolean isOn) { ... } /** * 判断PenddingIntent是否为空 * 如果为空,则定时器未启动,返回false */ public static boolean isServiceAlarmOn(Context context) { Intent i = PollService.newIntent(context); PendingIntent pi = PendingIntent .getService(context, 0, i, PendingIntent.FLAG_NO_CREATE); return pi != null; } ... }
这里的PendingIntent仅用于设置定时器,因此PendingIntent空值表示定时器还未设置。
控制定时器
既然可以开关定时器(也能判定其启停状态),接下来就通过图形界面对其进行开关控制。首先添加另一菜单项。
menu/is_reading_menu.xml:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_item_toggle_polling" android:title="@string/start_polling" app:showAsAction="ifRoom" /> </menu>
添加字符串资源
<string name="start_polling">Start polling</string> <string name="stop_polling">Stop polling</string> <string name="new_pictures_title">New PhotoGallery Pictures</string> <string name="new_pictures_text">You have new pictures in PhotoGallery.</string>
菜单项切换实现
FragmentThree.java :
private static final String TAG = "PhotoGalleryFragment"; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... updateItems(); // PollService.setServiceAlarm(getActivity(), true); ... } ... @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_toggle_polling: boolean shouldStartAlarm = !PullService.isServiceAlarmOn(getActivity()); PullService.setServiceAlarm(getActivity(), shouldStartAlarm); getActivity().invalidateOptionsMenu(); //刷新工具栏菜单 return true; default: return super.onOptionsItemSelected(item); } } } ...
完成代码添加后,就应该可以启停定时器了。然而,你可能已经注意到,即使定时器已经启动了, polling选项菜单也总是显示着Start polling。我们应该更新选项菜单,以准确反映定时器启停状态。
在onCreateOptionsMenu(…)方法中,检查定时器的开关状态,然后相应地更新menu_item_toggle_polling的标题文字,反馈正确的信息给用户。
public class FragmentThree extends Fragment { private static final String TAG = "PhotoGalleryFragment"; ... @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.is_reading_menu, menu); MenuItem toggleItem = menu.findItem(R.id.menu_item_toggle_polling); if (PullService.isServiceAlarmOn(getActivity())) { toggleItem.setTitle(R.string.stop_polling); } else { toggleItem.setTitle(R.string.start_polling); } } ... }
通知信息
后台的服务完成了,但是用户对此毫不知情,因此我们需要服务与用户进行沟通。首先是基础代码准备。在MainActivity.java中,添加一个newIntent(…)静态方法,该静态方法会返回一个可用来启动PhotoGalleryActivity的Intent实例。(最后, PullService会调用这个方法,把返回结果封装在一个PendingIntent中,然后设置给通知消息。)
MainActivity.java :
public class MainActivity extends AppCompatActivity { public static Intent newIntent(Context context) { return new Intent(context, PhotoGalleryActivity.class); } ... }
一旦有了新结果,就让PullService通知用户。也就是说,创建一个Notification对象,然后调用NotificationManager.notify(int, Notification)方法。
PullService.java :
public class PullService extends IntentService { ... @Override protected void onHandleIntent(Intent intent) { Log.i(TAG, "Received an intent: " + intent); if(!isNetworkAvailableAndConnected()){ return; } //获得上一次结果的第一条数据的ID String lastResultId = QueryPrefrences.getLastResultId(this); List<MeiZi.ResultsBean> items = new ArrayList<>(); //获取最新返回集 items = new FlickrFetchr().fetchRecentPhotos(); if (items.size() == 0) { return; } //如果有结果,抓取第一条结果 String resultId = items.get(0).get_id(); /** * 确认是否不同于上一次结果id */ if(resultId.equals(lastResultId)){ Log.i(TAG, "Got an old result: " + resultId); }else{ Log.i(TAG, "Got a new result: " + resultId); /** * 发送一条通知 */ Resources resources = getResources(); Intent i = MainActivity.newIntent(this); PendingIntent pi = PendingIntent.getActivity(this,0,i,0); Notification notification = new NotificationCompat.Builder(this) .setTicker(resources.getString(R.string.new_pictures_title)) .setSmallIcon(android.R.drawable.ic_menu_report_image) .setContentTitle(resources.getString(R.string.new_pictures_title)) .setContentText(resources.getString(R.string.new_pictures_text)) .setContentIntent(pi) .setAutoCancel(true) .build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.notify(0,notification); } //将第一条结果存入SharedPreference QueryPrefrences.setLastResultId(this, resultId); } ... }
运行应用并打开polling服务。不一会儿,应该就会看到状态栏的通知图标。拉开通知抽屉,就会看到后台服务发送的新结果消息。
既然后台服务已能正常工作,那就改用一个更为合理的定时器常量。
public class PullService extends IntentService {u private static final String TAG = "PollService"; //public static final int POLL_INTERVAL = 1000 * 60; // 60 seconds private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES; ... }
最后的结果就是这样啦!如下所示:
查看Log可以发现,服务一致在后台运行,对网站进行轮询。获得最新的一次返回结果。
一旦有了最新的结果,我们就会在通知栏收到一条通知。
点击通知将回跳到我们的界面,还记得在MainActivity里面添加的NewIntent()方法吗?我们将打开MainActivity。
源代码地址
相关文章推荐
- android双后台服务,消息通知类(service1)
- android双后台服务,消息通知类(service2)
- android 后台运行服务之报告工作状态篇
- Android 软件升级(后台服务 + 通知栏进度条)
- Android App后台服务报告工作状态实例
- android后台服务service全解析(中)--IntentService与Notification前台通知
- Android程序后台开启服务,显示通知栏
- android 后台服务定时通知
- android 后台运行服务之发送工作请求给后台服务篇
- android双后台服务,消息通知类
- android 后台服务、通知信息
- android 双后台服务,消息通知 (mainActivity)
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的守护
- Android四大组件之—— 使用服务进行后台操作
- IE停止按钮是否影响后台服务工作?
- 创建一个免费的Android推送通知服务帐户
- Android Service 后台服务之本地服务
- Android 利用广播机制来进行SERVICE后台服务进程的守护
- Android移动后端服务(BAAS)快速搭建后台服务器之Bmob-android学习之旅(75)