Android 无互联网连接时暂停的 Intent 服务
Android Intent Service that pauses when no internet connection
我有一个要将数据库 ID 传递给的意图服务。然后该服务从数据库中获取相关行。然后它使用 volley 将此数据发送到 Web 服务器。
Handle Intent 方法检查互联网连接,如果找到 none,则在再次检查之前使线程休眠。这感觉很不对劲,但我确实需要等待互联网的服务。
我还需要服务按填充顺序处理工作队列。
这是当前代码。有没有更好的方法来处理这种情况?
public class CommandUploadService extends IntentService {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
private ServiceCallbacks serviceCallbacks;
public void setCallbacks(ServiceCallbacks callbacks) {
serviceCallbacks = callbacks;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public CommandUploadService getService() {
// Return this instance of LocalService so clients can call public methods
return CommandUploadService.this;
}
}
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_UPLOAD_COMMAND = "com.brandfour.tooltracker.services.action.UPLOAD_COMMAND";
// TODO: Rename parameters
private static final String ID = "com.brandfour.tooltracker.services.id";
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionUploadCommand(Context context, String actionID) {
Intent intent = new Intent(context, CommandUploadService.class);
intent.setAction(ACTION_UPLOAD_COMMAND);
intent.putExtra(ID, actionID);
context.startService(intent);
}
/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
*
* @param intent
* @see Service#onBind
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public CommandUploadService() {
super("CommandUploadService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_UPLOAD_COMMAND.equals(action)) {
final String id = intent.getStringExtra(ID);
handleActionUpload(id);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionUpload(String actionID) {
final ActionCommand ac = new RushSearch().whereId(actionID).findSingle(ActionCommand.class);
serviceCallbacks.refreshList();
ConnectionManager manager = new ConnectionManager();
Boolean connected = manager.isNetworkOnline(this);
while (connected == false) {
connected = manager.isNetworkOnline(this);
SystemClock.sleep(10000);
}
JSONObject json = new JSONObject();
JSONObject wrapper = new JSONObject();
try {
json.put("Command", ac.getCommand());
json.put("TimeStamp", ac.getTimeStamp());
json.put("State", ac.getState());
wrapper.put("command", json);
} catch (Exception e) {
}
String url = "[ommitted]";
JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST,
url, wrapper,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
ac.setState("SENT");
ac.save();
serviceCallbacks.complete();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d("BRANDFOUR", "Error: " + error.getMessage());
}
}) {
@Override
public String getBodyContentType() {
return "application/json; charset=utf-8";
}
};
// Adding request to request queue
RequestQueue queue = Volley.newRequestQueue(this);
queue.add(jsonObjReq);
}
public interface ServiceCallbacks {
void complete();
void refreshList();
}
}
您不应该轮询互联网,而应该监听(通过 BroadcastReceiver)连接状态何时发生变化:
@
http://developer.android.com/reference/android/net/ConnectivityManager.html
摘录
public static final String CONNECTIVITY_ACTION
Added in API level 1
A change in network connectivity has occurred. A default connection has either been established or lost. The NetworkInfo for the affected network is sent as an extra; it should be consulted to see what kind of connectivity event occurred.
If this is a connection that was the result of failing over from a disconnected network, then the FAILOVER_CONNECTION boolean extra is set to true.
For a loss of connectivity, if the connectivity manager is attempting to connect (or has already connected) to another network, the NetworkInfo for the new network is also passed as an extra. This lets any receivers of the broadcast know that they should not necessarily tell the user that no data traffic will be possible. Instead, the receiver should expect another broadcast soon, indicating either that the failover attempt succeeded (and so there is still overall data connectivity), or that the failover attempt failed, meaning that all connectivity has been lost.
For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY is set to true if there are no connected networks at all.
Constant Value: "android.net.conn.CONNECTIVITY_CHANGE"
如果您没有互联网连接,请不要暂停 IntentService
或 Thread
,请使用 BroadcastReceiver
作为 android.net.conn.CONNECTIVITY_CHANGE
。
因此,如果您调用您的 Intent 服务,请先检查网络可用性,如果不是,则将您的数据库 ID 保存在 SharedPreferences
或应用程序数据库中,当您的 BroadcastReceiver
显示有效的互联网连接时,从数据库或 SharedPreferences 获取数据并开始您的意向服务。
更新
在您的应用程序中创建数据库 table,使用 _id、name(可选)、 HTTP(s) url、status 和您的数据库 foreign_key_id(当前您正在使用)
在 activity 或应用程序级别(首选应用程序级别)注册您的 BroadcastReceiver
,每当您尝试发送数据时,首先检查互联网连接是否可用,否则将您当前的请求添加到上面创建数据库 table.
现在您同时拥有数据库 table(其中包含所有不完整的 HTTP 请求)和广播接收器,当您的 phone 连接到互联网时,您的广播接收器将比简单地启动您的 CommandUploadService
并获取所有状态不完整的行并执行您的 HTTP 请求并更新状态为完整的行。
如果请求的顺序不重要,我会简单地取消请求并重新启动相同的 IntentService
作为 onHandleIntent
的一部分。像这样:
protected void onHandleIntent(Intent intent) {
// ... Code to get the actionID ...
Boolean connected = manager.isNetworkOnline(this);
if (!connected) {
this.startService(intent);
return;
}
// .. connected to internet, run code that fires the request ...
}
但是这种方法会导致由于连接问题而无法处理的当前请求被放在工作队列的末尾,并且您声明这破坏了您的逻辑。
此方法的另一个问题是,您会不断重启服务,直到互联网连接恢复,这可能会耗尽电池电量。
现在,另一种解决方案可能是放弃您的 IntentService
并改为创建常规 Service
。让我们将此服务命名为 UploadService
。 UploadService
应该启动(保持它 运行)但也使用服务绑定(用于通信目的)。
UploadService
应该管理一个内部工作队列,以确保您的请求以正确的顺序处理。您应该公开一个方法来通过 IBinder
实现对请求进行排队。
UploadService
的主要功能应该是获取(但不删除!- 使用 peek()
)队列前端的方法。让我们将此方法命名为 handleRequest
。如果队列为空,则应关闭 UploadService
。如果队列不为空,您应该生成一个 AsyncTask
来处理放在队列前面的请求。如果请求被成功处理,您将在 onPostExecute
期间移除队列的前端并重新调用 handleRequest
以检查是否有其他请求排队。如果请求失败 - 很可能是由于互联网连接丢失 - 你在 onPostExecute
期间不要删除前面的元素。相反,您检查互联网连接是否已丢失。如果确实如此,您注册一个 BroadcastReceiver
来侦听互联网连接。此 BroadcastReceiver
应在再次建立互联网连接以恢复处理请求时调用 handleRequest
。
上述方法的伪代码实现如下所示:
public class UploadService extends Service {
private final BroadtcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()) {
boolean connected;
// Use extras to verify that connection has been re-established...
if (connected) {
// Unregister until we lose network connectivity again.
UploadService.this.unregisterReceiver(this);
// Resume handling requests.
UploadService.this.handleRequest();
}
}
}
};
private final Queue<RequestData> mRequestQueue = new XXXQueue<RequestData>(); // Choose Queue implementation.
private final UploadServiceBinder mBinder = new UploadServiceBinder();
public class UploadServiceBinder extends Binder {
public void enqueueRequest(RequestData requestData) {
UploadService.this.mRequestQueue.offer(requestData);
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private void handleRequest() {
RequestData request = mRequestsQueue.peek();
if (request == null) {
// No more requests to process.
// Shutdown self.
stopSelf();
} else {
// Process the request at the head of the queue.
new Request().execute(request);
}
}
private class Request extends AsyncTask<RequestData, Void, Boolean> {
@Override
protected void doInBackground(RequestData... requests) {
try {
// ... Code that executes the web request ...
// Return true if request succeeds.
return true;
} catch(IOException ioe) {
// Request failed, return false.
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Remove request from work queue.
UploadService.this.mRequestQueue.remove();
// Continue by processing next request.
UploadService.this.handleRequest();
} else {
// Request failed, properly due to network error.
// Keep request at the head of the queue, i.e. do not remove it from the queue.
// Check current internet connectivity
ConnectionManager manager = new ConnectionManager();
boolean connected = manager.isNetworkOnline(UploadService.this);
if (connected) {
// If connected, something else went wrong.
// Retry request right away.
UploadService.this.handleRequest();
} else {
// Lack of internet.
// Register receiver in order to resume processing requests once internet connectivity is restored.
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
UploadService.this.registerReceiver(UploadService.this.mReceiver, filter);
}
}
}
}
}
我有一个要将数据库 ID 传递给的意图服务。然后该服务从数据库中获取相关行。然后它使用 volley 将此数据发送到 Web 服务器。
Handle Intent 方法检查互联网连接,如果找到 none,则在再次检查之前使线程休眠。这感觉很不对劲,但我确实需要等待互联网的服务。
我还需要服务按填充顺序处理工作队列。
这是当前代码。有没有更好的方法来处理这种情况?
public class CommandUploadService extends IntentService {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
private ServiceCallbacks serviceCallbacks;
public void setCallbacks(ServiceCallbacks callbacks) {
serviceCallbacks = callbacks;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public CommandUploadService getService() {
// Return this instance of LocalService so clients can call public methods
return CommandUploadService.this;
}
}
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_UPLOAD_COMMAND = "com.brandfour.tooltracker.services.action.UPLOAD_COMMAND";
// TODO: Rename parameters
private static final String ID = "com.brandfour.tooltracker.services.id";
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionUploadCommand(Context context, String actionID) {
Intent intent = new Intent(context, CommandUploadService.class);
intent.setAction(ACTION_UPLOAD_COMMAND);
intent.putExtra(ID, actionID);
context.startService(intent);
}
/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
*
* @param intent
* @see Service#onBind
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public CommandUploadService() {
super("CommandUploadService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_UPLOAD_COMMAND.equals(action)) {
final String id = intent.getStringExtra(ID);
handleActionUpload(id);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionUpload(String actionID) {
final ActionCommand ac = new RushSearch().whereId(actionID).findSingle(ActionCommand.class);
serviceCallbacks.refreshList();
ConnectionManager manager = new ConnectionManager();
Boolean connected = manager.isNetworkOnline(this);
while (connected == false) {
connected = manager.isNetworkOnline(this);
SystemClock.sleep(10000);
}
JSONObject json = new JSONObject();
JSONObject wrapper = new JSONObject();
try {
json.put("Command", ac.getCommand());
json.put("TimeStamp", ac.getTimeStamp());
json.put("State", ac.getState());
wrapper.put("command", json);
} catch (Exception e) {
}
String url = "[ommitted]";
JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST,
url, wrapper,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
ac.setState("SENT");
ac.save();
serviceCallbacks.complete();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d("BRANDFOUR", "Error: " + error.getMessage());
}
}) {
@Override
public String getBodyContentType() {
return "application/json; charset=utf-8";
}
};
// Adding request to request queue
RequestQueue queue = Volley.newRequestQueue(this);
queue.add(jsonObjReq);
}
public interface ServiceCallbacks {
void complete();
void refreshList();
}
}
您不应该轮询互联网,而应该监听(通过 BroadcastReceiver)连接状态何时发生变化: @ http://developer.android.com/reference/android/net/ConnectivityManager.html
摘录
public static final String CONNECTIVITY_ACTION
Added in API level 1
A change in network connectivity has occurred. A default connection has either been established or lost. The NetworkInfo for the affected network is sent as an extra; it should be consulted to see what kind of connectivity event occurred.
If this is a connection that was the result of failing over from a disconnected network, then the FAILOVER_CONNECTION boolean extra is set to true.
For a loss of connectivity, if the connectivity manager is attempting to connect (or has already connected) to another network, the NetworkInfo for the new network is also passed as an extra. This lets any receivers of the broadcast know that they should not necessarily tell the user that no data traffic will be possible. Instead, the receiver should expect another broadcast soon, indicating either that the failover attempt succeeded (and so there is still overall data connectivity), or that the failover attempt failed, meaning that all connectivity has been lost.
For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY is set to true if there are no connected networks at all.
Constant Value: "android.net.conn.CONNECTIVITY_CHANGE"
如果您没有互联网连接,请不要暂停 IntentService
或 Thread
,请使用 BroadcastReceiver
作为 android.net.conn.CONNECTIVITY_CHANGE
。
因此,如果您调用您的 Intent 服务,请先检查网络可用性,如果不是,则将您的数据库 ID 保存在 SharedPreferences
或应用程序数据库中,当您的 BroadcastReceiver
显示有效的互联网连接时,从数据库或 SharedPreferences 获取数据并开始您的意向服务。
更新
在您的应用程序中创建数据库 table,使用 _id、name(可选)、 HTTP(s) url、status 和您的数据库 foreign_key_id(当前您正在使用)
在 activity 或应用程序级别(首选应用程序级别)注册您的 BroadcastReceiver
,每当您尝试发送数据时,首先检查互联网连接是否可用,否则将您当前的请求添加到上面创建数据库 table.
现在您同时拥有数据库 table(其中包含所有不完整的 HTTP 请求)和广播接收器,当您的 phone 连接到互联网时,您的广播接收器将比简单地启动您的 CommandUploadService
并获取所有状态不完整的行并执行您的 HTTP 请求并更新状态为完整的行。
如果请求的顺序不重要,我会简单地取消请求并重新启动相同的 IntentService
作为 onHandleIntent
的一部分。像这样:
protected void onHandleIntent(Intent intent) {
// ... Code to get the actionID ...
Boolean connected = manager.isNetworkOnline(this);
if (!connected) {
this.startService(intent);
return;
}
// .. connected to internet, run code that fires the request ...
}
但是这种方法会导致由于连接问题而无法处理的当前请求被放在工作队列的末尾,并且您声明这破坏了您的逻辑。
此方法的另一个问题是,您会不断重启服务,直到互联网连接恢复,这可能会耗尽电池电量。
现在,另一种解决方案可能是放弃您的 IntentService
并改为创建常规 Service
。让我们将此服务命名为 UploadService
。 UploadService
应该启动(保持它 运行)但也使用服务绑定(用于通信目的)。
UploadService
应该管理一个内部工作队列,以确保您的请求以正确的顺序处理。您应该公开一个方法来通过 IBinder
实现对请求进行排队。
UploadService
的主要功能应该是获取(但不删除!- 使用 peek()
)队列前端的方法。让我们将此方法命名为 handleRequest
。如果队列为空,则应关闭 UploadService
。如果队列不为空,您应该生成一个 AsyncTask
来处理放在队列前面的请求。如果请求被成功处理,您将在 onPostExecute
期间移除队列的前端并重新调用 handleRequest
以检查是否有其他请求排队。如果请求失败 - 很可能是由于互联网连接丢失 - 你在 onPostExecute
期间不要删除前面的元素。相反,您检查互联网连接是否已丢失。如果确实如此,您注册一个 BroadcastReceiver
来侦听互联网连接。此 BroadcastReceiver
应在再次建立互联网连接以恢复处理请求时调用 handleRequest
。
上述方法的伪代码实现如下所示:
public class UploadService extends Service {
private final BroadtcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()) {
boolean connected;
// Use extras to verify that connection has been re-established...
if (connected) {
// Unregister until we lose network connectivity again.
UploadService.this.unregisterReceiver(this);
// Resume handling requests.
UploadService.this.handleRequest();
}
}
}
};
private final Queue<RequestData> mRequestQueue = new XXXQueue<RequestData>(); // Choose Queue implementation.
private final UploadServiceBinder mBinder = new UploadServiceBinder();
public class UploadServiceBinder extends Binder {
public void enqueueRequest(RequestData requestData) {
UploadService.this.mRequestQueue.offer(requestData);
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private void handleRequest() {
RequestData request = mRequestsQueue.peek();
if (request == null) {
// No more requests to process.
// Shutdown self.
stopSelf();
} else {
// Process the request at the head of the queue.
new Request().execute(request);
}
}
private class Request extends AsyncTask<RequestData, Void, Boolean> {
@Override
protected void doInBackground(RequestData... requests) {
try {
// ... Code that executes the web request ...
// Return true if request succeeds.
return true;
} catch(IOException ioe) {
// Request failed, return false.
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Remove request from work queue.
UploadService.this.mRequestQueue.remove();
// Continue by processing next request.
UploadService.this.handleRequest();
} else {
// Request failed, properly due to network error.
// Keep request at the head of the queue, i.e. do not remove it from the queue.
// Check current internet connectivity
ConnectionManager manager = new ConnectionManager();
boolean connected = manager.isNetworkOnline(UploadService.this);
if (connected) {
// If connected, something else went wrong.
// Retry request right away.
UploadService.this.handleRequest();
} else {
// Lack of internet.
// Register receiver in order to resume processing requests once internet connectivity is restored.
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
UploadService.this.registerReceiver(UploadService.this.mReceiver, filter);
}
}
}
}
}