运行 IntentService 中的任务时出现 NetworkOnMainThreadException

NetworkOnMainThreadException when running task in IntentService

我正在尝试使用 IntentService 执行文件下载任务。我读到 IntentService 将创建一个工作线程并执行请求的任务。以下代码将下载一个视频文件。

public class MyServiceUsingIntentService extends IntentService{

    private int count=0;

    public MyServiceUsingIntentService() {
        super("MyServiceUsingIntentService");
    }

    public MyServiceUsingIntentService(String name) {
        super("MyServiceUsingIntentService");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

                try {
                    URL url = new URL("http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4");
                    URLConnection urlConnection = url.openConnection();
                    urlConnection.setReadTimeout(5000);

                    debugMessage("urlConnection.getContentLength : " + urlConnection.getContentLength());

                    InputStream readStream = urlConnection.getInputStream();
                    String filename = "/sdcard/sample" + count++ +".mp4";
                    OutputStream writeStream = new FileOutputStream(filename);
                    int i;
                    byte[] byteArray = new byte[153600];
                    while((i = readStream.read(byteArray)) != -1){
                        writeStream.write(byteArray,0,i);
                    }
                    writeStream.close();
                    readStream.close();

                    debugMessage("Download done in doInBackground");
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }    
        return START_STICKY;
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

    }

    public static void debugMessage(String message){
        Log.d("MyServiceUsingIntentService",message);
    }

}

当我从 MainActivity 调用 startService 时

startService(new Intent(MainActivity.this, MyServiceUsingIntentService.class));

我收到 NetworkOnMainThreadException 错误:

12-30 23:51:04.305 9586-9586/oneplus.app7 D/AndroidRuntime: Shutting down VM
12-30 23:51:04.306 9586-9586/oneplus.app7 E/AndroidRuntime: FATAL EXCEPTION: main
                                                            Process: oneplus.app7, PID: 9586
                                                            java.lang.RuntimeException: Unable to start service oneplus.app7.MyServiceUsingIntentService@be390c with Intent { cmp=oneplus.app7/.MyServiceUsingIntentService }: android.os.NetworkOnMainThreadException
                                                                at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3479)
                                                                at android.app.ActivityThread.-wrap21(ActivityThread.java)
                                                                at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1657)
                                                                at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                at android.os.Looper.loop(Looper.java:154)
                                                                at android.app.ActivityThread.main(ActivityThread.java:6334)
                                                                at java.lang.reflect.Method.invoke(Native Method)
                                                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                                                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
                                                             Caused by: android.os.NetworkOnMainThreadException
                                                                at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1303)
                                                                at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:86)
                                                                at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:74)
                                                                at java.net.InetAddress.getAllByName(InetAddress.java:752)
                                                                at com.android.okhttp.internal.Network.resolveInetAddresses(Network.java:29)
                                                                at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:187)
                                                                at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:156)
                                                                at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:98)
                                                                at com.android.okhttp.internal.http.HttpEngine.createNextConnection(HttpEngine.java:346)
                                                                at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:329)
                                                                at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:247)
                                                                at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:457)
                                                                at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:405)
                                                                at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getHeaders(HttpURLConnectionImpl.java:162)
                                                                at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getHeaderField(HttpURLConnectionImpl.java:206)
                                                                at java.net.URLConnection.getHeaderFieldLong(URLConnection.java:628)
                                                                at java.net.URLConnection.getContentLengthLong(URLConnection.java:500)
                                                                at java.net.URLConnection.getContentLength(URLConnection.java:484)
                                                                at oneplus.app7.MyServiceUsingIntentService.onStartCommand(MyServiceUsingIntentService.java:54)
                                                                at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3462)
                                                                at android.app.ActivityThread.-wrap21(ActivityThread.java) 
                                                                at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1657) 
                                                                at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                at android.os.Looper.loop(Looper.java:154) 
                                                                at android.app.ActivityThread.main(ActivityThread.java:6334) 
                                                                at java.lang.reflect.Method.invoke(Native Method) 
                                                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
                                                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

但如果我尝试 运行 在 onStartCommand() 内的线程中下载部分,它工作正常,我能够下载文件。

是否每次IntentService都需要在单独的线程中进行网络操作?

您应该通过覆盖 onHandleIntent() 方法在不同的线程上进行处理,而不应覆盖 onStartCommand()

编辑

您可以看到 here IntentService 如何为您处理 onStartCommand,因此您不必这样做。

您的代码应该在

中执行
onHandleIntent()

方法。这是用于生成工作线程并对其执行工作的方法。

我还建议尝试将您的工作转移到

JobIntentService

因为Android O 及更高版本将有一个限制,当应用程序不在前台或有任何可见组件时调用 startService 将抛出异常。

JobIntentService 与 IntentService 完全相似。不同之处在于它将在支持 JobScheduler 的设备上内部使用 JobScheduler api。只需覆盖

onHandleWork

方法并像IntentService一样使用它。使用

enqueueWork 

开始工作而不是 startService

一般情况下,您不能在主线程(UI 线程)中进行网络调用,因此会出现 NetworkOnMainThreadException。

IntentService确实从主线程卸载了任务。但是你必须执行 onHandleIntent(Intent).

检查文档:

请记住,任务将按顺序处理。如果你有什么更灵活的控制使用原始服务并自己管理线程。

如文档中所述:

onStartCommand

You should not override this method for your IntentService. Instead, override onHandleIntent(Intent), which the system calls when the IntentService receives a start request.

似乎在主线程上使用了onStartCommand,而在工作线程上处理了onHandleIntent:

This "work queue processor" pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

参考

https://developer.android.com/reference/android/app/IntentService.html https://developer.android.com/reference/android/app/IntentService.html#onStartCommand(android.content.Intent, int, int)