網際網路的程序到底應該如何在非MainThread執行

NetworkOnMainThreadException就是將網際網路的程序直接在MainThread中執行, 在Android 4+之後, 強制要求不得將網際網路的程序直接執行, 除了前面單元使用的Thread處理之外, 到底還有哪些方式, 可以正常運作呢? 就讓我來為各位一次做完這些實驗.

先來一段處理網際網路的程序, 以方便後續的實驗.


private void useInternet(){
    try{
        URL url = new URL("https://www.bradchao.com");
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.connect();
        conn.getInputStream();
        Log.v("brad", "OK");
    }catch (Exception e){
        Log.v("brad", e.toString());
    }
}
							

以Thread來處理

以上這一個方法, 只要是直接呼叫引用, 將會直接拋出例外: android.os.NetworkOnMainThreadException
依照前面單元的介紹, 就直接以一個Thread來處理就可以. 如下:

new Thread(){
    @Override
    public void run() {
        useInternet();
    }
}.start();							
							

以Timer/TimerTask來處理

將網際網路的任務, 放在週期任務的 run() 中執行, 也可以正常運作, 如下程式:

public void test6(View view) {
    timer.schedule(new MyTimerTask(),0);
}

private class MyTimerTask extends TimerTask {
    @Override
    public void run() {
        useInternet();
    }
}
							

以AsyncTask來處理 --- 強烈建議使用

將網際網路的任務, 放在自訂的AsyncTask的 doInBackground() 中執行, 也可以正常運作, 如下程式:

public void test5(View view) {
    MyAsyncTask myAsyncTask = new MyAsyncTask();
    myAsyncTask.execute();
}

private class MyAsyncTask extends AsyncTask < Void,Void,Void > {
    @Override
    protected Void doInBackground(Void... voids) {
        useInternet();
        return null;
    }
}
							

無法直接以啟動型Service來處理

將網際網路的任務, 放在啟動型Service中執行, 也將會拋出NetworkOnMainThread的例外異常, 如下定義的MyService1程式:

package tw.brad.connectnetworktest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import java.net.HttpURLConnection;
import java.net.URL;

public class MyService1 extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        useInternet();
        return super.onStartCommand(intent, flags, startId);
    }

    private void useInternet(){
        try{
            URL url = new URL("https://www.bradchao.com");
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.connect();
            conn.getInputStream();
            Log.v("brad", "OK");
        }catch (Exception e){
            Log.v("brad", e.toString());
        }
    }
}
							
接著回到MainActivity中, 如下來啟動:

public void test2(View view) {
    Intent intent = new Intent(this, MyService1.class);
    startService(intent);
}
							

無法直接以綁定型Service來處理

將網際網路的任務, 放在綁定型Service中執行, 也將會拋出NetworkOnMainThread的例外異常, 如下定義的MyService2程式:

package tw.brad.connectnetworktest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import java.net.HttpURLConnection;
import java.net.URL;

public class MyService2 extends Service {
    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {
        MyService2 getService() {
            return MyService2.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public void useInternet(){
        try{
            URL url = new URL("https://www.bradchao.com");
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.connect();
            conn.getInputStream();
            Log.v("brad", "OK");
        }catch (Exception e){
            Log.v("brad", e.toString());
        }
    }
}
							
接著回到MainActivity中, 在 test3(View view)處理引用呼叫:

private MyService2 mService2;
private boolean mBound = false;

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className,
                                   IBinder service) {
        MyService2.LocalBinder binder = (MyService2.LocalBinder) service;
        mService2 = binder.getService();
        mBound = true;
    }
    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        mBound = false;
    }
};

@Override
protected void onStart() {
    super.onStart();
    Intent intent = new Intent(this, MyService2.class);
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}

@Override
protected void onStop() {
    super.onStop();
    if (mBound) {
        unbindService(mConnection);
        mBound = false;
    }
}

public void test3(View view) {
    mService2.useInternet();
}
							

可以用IntentService來處理 --- 也很好喔

將網際網路的任務, 可以以背景的IntentService來處理執行, 先如下MyService3

package tw.brad.connectnetworktest;

import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
import android.util.Log;

import java.net.HttpURLConnection;
import java.net.URL;

public class MyService3 extends IntentService {
    public MyService3() {
        super("MyService3");
    }

    public static void startServiceJob(Context context) {
        Intent intent = new Intent(context, MyService3.class);
        context.startService(intent);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            useInternet();
        }
    }
    //  background service
    private void useInternet(){
        try{
            URL url = new URL("https://www.bradchao.com");
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.connect();
            conn.getInputStream();
            Log.v("brad", "OK");
        }catch (Exception e){
            Log.v("brad", e.toString());
        }
    }

}
							
回到MainActivity中

    public void test4(View view) {
        MyService3.startServiceJob(this);
    }							
							

影片教學

有空補上喔.

影片重點提示