Android Intent Service在没有互联网连接时暂停

3
我有一个意图服务,我将数据库ID传递给它。服务然后从数据库中获取相关行。 然后使用volley将此数据发送到Web服务器。
Handle Intent方法检查是否有Internet连接,如果没有,则在再次检查之前使线程休眠。这感觉不对劲而且不好,但我确实需要该服务等待Internet。
我还需要该服务按照填充顺序处理工作队列。
以下是当前的代码。 有更好的处理此场景的方法吗?
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();
}

}


1
你不能在网络可用时调用你的服务,而不是让它睡眠吗? - cafebabe1991
在你的服务中使用广播接收器。 - Anis LOUNIS aka AnixPasBesoin
服务通过函数调用启动,传递了一些数据。如果服务是作为响应广播而启动的,如何检索这些数据? - Sergio
我无法理解你的主要目标,建议很好,但我不明白你真正想要什么。 - Sheychan
3个回答

1
不要暂停你的IntentServiceThread,即使没有网络连接,使用BroadcastReceiver来监听android.net.conn.CONNECTIVITY_CHANGE。因此,如果您调用了意图服务,请首先检查网络可用性,如果没有,则将数据库ID保存在SharedPreferences或应用程序数据库中,当您的BroadcastReceiver显示有效的Internet连接时,从数据库或SharedPreferences获取数据并启动您的意图服务。 更新 在您的应用程序中创建一个数据库表,包括_id名称(可选)、HTTP(s) URL状态和您的数据库foreign_key_id(目前正在使用)。
在活动或应用程序级别上注册您的BroadcastReceiver(最好是应用程序级别),每当您尝试发送数据时,请首先检查Internet连接是否可用,如果可用,请发送;否则,请将当前请求添加到上述创建的数据库表中。
现在你拥有了数据库表(其中包含所有不完整的HTTP请求)和广播接收器,当你的手机连接到互联网时,广播接收器会通知你,然后简单地启动CommandUploadService并获取所有状态为不完整的行,执行你的HTTP请求并更新你的行状态为完成。

感谢您的反馈。那么我应该定义一个新的广播接收器,在我的服务中检查连接。如果没有连接,我将注册接收器。在接收器中,我会再次启动服务?我还会将数据库ID存储在存储的首选项中,并再次读取它吗? - Sergio
@HarisQureshi 我想 android.net.conn.CONNECTIVITY_CHANGE 的BroadcastReceiver,即使没有真正的互联网连接(例如:设备连接了WiFi路由器,但路由器没有互联网连接),也会被调用。那么如何处理这种情况? - Krishna
在你的类中添加布尔型网络连接状态 networkConnectivityStatus,然后在你的 NetworkConnectivity BroadcastReceiver 中更改它的值。 - Haris Qurashi

1
如果请求的顺序不重要,我会在onHandleIntent中取消请求并重新启动同一个IntentService。代码如下:

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);
                }
            }
        }
    }
}

谢谢。非常好的回答。 - Sergio
不用谢。请告诉我它在实践中是否有效(我自己没有尝试过)。 - Janus Varmarken
嗨。看起来它能工作了!还需要进行一些测试,但目前为止还不错。有一件事。目前服务是通过绑定来启动的。这意味着,即使工作队列中有项目,如果用户导航离开,服务也将被销毁。需要进行一些调整! - Sergio
很高兴听到它起作用了:)。是的,你也需要启动它。一个服务可以同时启动和绑定。 - Janus Varmarken

0

你不应该轮询互联网,而应该通过广播接收器监听连接状态的变化:

@ 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"

@JoxRraex 我认为针对 android.net.conn.CONNECTIVITY_CHANGE 的 BroadcastReceiver 会在没有真正的互联网连接时被调用(例如:设备已连接到 WiFi 路由器,但路由器没有互联网连接)。那么如何处理这种情况呢? - Krishna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接