java - studio - android thread教學




如何修復android.os.NetworkOnMainThreadException? (20)

我在為RssReader運行我的Android項目時遇到錯誤。

碼:

URL url = new URL(urlToRssFeed);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader xmlreader = parser.getXMLReader();
RssHandler theRSSHandler = new RssHandler();
xmlreader.setContentHandler(theRSSHandler);
InputSource is = new InputSource(url.openStream());
xmlreader.parse(is);
return theRSSHandler.getFeed();

它顯示以下錯誤:

android.os.NetworkOnMainThreadException

我該如何解決這個問題?



spektom的最佳答案是完美的。

如果您正在編寫AsyncTask內聯而不是作為類擴展,並且除此之外,如果需要從AsyncTask獲取響應,可以使用如下的get()方法。

RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();

(從他的例子。)


您應該幾乎總是在線程上運行網絡操作或作為異步任務。

但是,如果您願意接受後果, 可以刪除此限制並覆蓋默認行為。

加:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy); 

在你的班上,

在android manifest.xml文件中添加此權限:

<uses-permission android:name="android.permission.INTERNET"/>

後果:

您的應用程序將(在互聯網連接不穩定的區域)變得無法響應並鎖定,用戶感知緩慢並且必須執行強制終止,並且您冒著活動管理器殺死您的應用並告訴用戶該應用已停止的風險。

Android提供了一些關於良好編程實踐的好技巧,以設計響應能力: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.htmlhttp://developer.android.com/reference/android/os/NetworkOnMainThreadException.html


使用Android Annotations是一種選擇。 它允許您在後台線程中簡單地運行任何方法:

// normal method
private void normal() {
    doSomething(); // do something in background
}

@Background
protected void doSomething() 
    // run your networking code here
}

請注意,雖然它提供了簡單性和可讀性的好處,但它有其缺點。


只是明確說明一些事情:

主線程基本上是UI線程。

所以說你不能在主線程中進行網絡操作意味著你不能在UI線程中進行網絡操作,這意味著你不能在*runOnUiThread(new Runnable() { ... }*裡面做一些其他線程的網絡操作 ,或者。

(我只是想知道為什麼我在主線程以外的其他地方得到了這個錯誤。我只是有一個長期頭痛的時刻。這就是為什麼;這個線程幫助了;希望這個評論會幫助其他人。)


在另一個線程上執行網絡操作

例如:

new Thread(new Runnable(){
    @Override
    public void run() {
        // Do network action in this function
    }
}).start();

並將其添加到AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

如果執行任務花費太多時間,則會在主線程上執行任何繁重的任務,從而發生此異常。

為避免這種情況,我們可以使用線程執行器來處理它

Executors.newSingleThreadExecutor().submit(new Runnable() {
    @Override
    public void run() {
        // You can perform your task here.
    }
});

對我來說就是這樣:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="10" />

我測試我的應用程序的設備是4.1.2,這是SDK版本16!

確保目標版本與Android目標庫相同。 如果您不確定目標庫是什麼,請右鍵單擊您的項目 - > 構建路徑 - > Android ,它應該是勾選的那個。

此外,正如其他人所提到的,包括訪問Internet的正確權限:

<uses-permission android:name="android.permission.INTERNET"/>

您使用以下代碼禁用嚴格模式:

if (android.os.Build.VERSION.SDK_INT > 9) {
    StrictMode.ThreadPolicy policy = 
        new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
}

建議不 AsyncTask :使用AsyncTask接口。

這兩種方法的完整代碼


您無法在Honeycomb上的UI線程上執行網絡I/O 從技術上講,它可以在早期版本的Android上,但它是一個非常糟糕的主意,因為它會導致您的應用程序停止響應,並可能導致操作系統因為行為不當而導致您的應用程序被殺。 您需要運行後台進程或使用AsyncTask在後台線程上執行網絡事務。

Android開發者網站上有一篇關於無痛線程的文章,這是對此的一個很好的介紹,它將為您提供比這裡實際提供的更好的答案深度。


把你的代碼放在裡面:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();

要么:

class DemoTask extends AsyncTask<Void, Void, Void> {

    protected Void doInBackground(Void... arg0) {
        //Your implementation
    }

    protected void onPostExecute(Void result) {
        // TODO: do something with the feed
    }
}

接受的答案有一些重要的缺點。 除非你真的知道自己在做什麼,否則不建議使用AsyncTask進行網絡連接。 一些不利方麵包括:

  • 作為非靜態內部類創建的AsyncTask具有對封閉的Activity對象,其上下文以及該活動創建的整個View層次結構的隱式引用。 此引用可防止在AsyncTask的後台工作完成之前對Activity進行垃圾回收。 如果用戶的連接速度很慢,和/或下載量很大,則這些短期內存洩漏可能會成為問題 - 例如,如果方向發生多次更改(並且您沒有取消正在執行的任務),或者用戶導航遠離活動。
  • AsyncTask具有不同的執行特性,具體取決於它執行的平台:API級別4之前AsyncTasks在單個後台線程上串行執行; 從API級別4到API級別10,AsyncTasks在最多128個線程的池上執行; 從API級別11開始,AsyncTask在單個後台線程上串行執行(除非您使用重載的executeOnExecutor方法並提供替代執行程序)。 當在Gingerbread上同時執行時,在ICS上串行運行時運行正常的代碼可能會中斷,例如,如果您有無意的執行順序依賴項。

如果您想避免短期內存洩漏,在所有平台上都有明確定義的執行特性,並且有基礎來構建非常強大的網絡處理,您可能需要考慮:

  1. 使用一個能為你做得很好的庫 - 在這個問題中對網絡庫進行了很好的比較,或者
  2. 使用ServiceIntentService ,可能使用PendingIntent通過Activity的onActivityResult方法返回結果。

IntentService方法

下行方面:

  • AsyncTask更多的代碼和復雜性,雖然沒有你想像的那麼多
  • 將隊列請求排隊並在單個後台線程上運行它們。 您可以通過將IntentService替換為等效的Service實現(可能就像這個)來輕鬆控制它。
  • 嗯,實際上我現在想不出任何其他人

向上方面:

  • 避免短期內存洩漏問題
  • 如果您的活動在網絡操作正在進行時重新啟動,它仍然可以通過其onActivityResult方法接收下載結果
  • 比AsyncTask更好的平台來構建和重用強大的網絡代碼。 示例:如果您需要執行重要上傳,可以從Activity AsyncTask執行此AsyncTask ,但如果用戶上下文切換出應用程序以接聽電話,則係統可能會在上載完成之前終止應用程序。 使用活動Service終止應用程序的可能性較小
  • 如果您使用自己的並發版本的IntentService (就像我上面鏈接的那樣),您可以通過Executor控制並發級別。

實施摘要

您可以非常輕鬆地實現IntentService以在單個後台線程上執行下載。

步驟1:創建IntentService以執行下載。 你可以告訴它通過Intent extra's下載什麼,並傳遞一個PendingIntent來用來將結果返回給Activity

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

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

public class DownloadIntentService extends IntentService {

    private static final String TAG = DownloadIntentService.class.getSimpleName();

    public static final String PENDING_RESULT_EXTRA = "pending_result";
    public static final String URL_EXTRA = "url";
    public static final String RSS_RESULT_EXTRA = "url";

    public static final int RESULT_CODE = 0;
    public static final int INVALID_URL_CODE = 1;
    public static final int ERROR_CODE = 2;

    private IllustrativeRSSParser parser;

    public DownloadIntentService() {
        super(TAG);

        // make one and re-use, in the case where more than one intent is queued
        parser = new IllustrativeRSSParser();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT_EXTRA);
        InputStream in = null;
        try {
            try {
                URL url = new URL(intent.getStringExtra(URL_EXTRA));
                IllustrativeRSS rss = parser.parse(in = url.openStream());

                Intent result = new Intent();
                result.putExtra(RSS_RESULT_EXTRA, rss);

                reply.send(this, RESULT_CODE, result);
            } catch (MalformedURLException exc) {
                reply.send(INVALID_URL_CODE);
            } catch (Exception exc) {
                // could do better by treating the different sax/xml exceptions individually
                reply.send(ERROR_CODE);
            }
        } catch (PendingIntent.CanceledException exc) {
            Log.i(TAG, "reply cancelled", exc);
        }
    }
}

第2步:在清單中註冊服務:

<service
        android:name=".DownloadIntentService"
        android:exported="false"/>

步驟3:從Activity調用服務,傳遞PendingResult對象,Service將使用該對象返回結果:

PendingIntent pendingResult = createPendingResult(
    RSS_DOWNLOAD_REQUEST_CODE, new Intent(), 0);
Intent intent = new Intent(getApplicationContext(), DownloadIntentService.class);
intent.putExtra(DownloadIntentService.URL_EXTRA, URL);
intent.putExtra(DownloadIntentService.PENDING_RESULT_EXTRA, pendingResult);
startService(intent);

第4步:處理onActivityResult中的結果:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RSS_DOWNLOAD_REQUEST_CODE) {
        switch (resultCode) {
            case DownloadIntentService.INVALID_URL_CODE:
                handleInvalidURL();
                break;
            case DownloadIntentService.ERROR_CODE:
                handleError(data);
                break;
            case DownloadIntentService.RESULT_CODE:
                handleRSS(data);
                break;
        }
        handleRSS(data);
    }
    super.onActivityResult(requestCode, resultCode, data);
}

這裡提供了一個包含完整的Android-Studio / gradle項目的github項目。


簡單來說,

不要在UI線程中進行網絡工作

例如,如果您執行HTTP請求,那就是網絡操作。

解:

  1. 你必須創建一個新的線程
  2. 或者使用AsyncTask類

辦法:

把你所有的作品都放進去

  1. 新線程的run()方法
  2. AsyncTask類的doInBackground()方法。

但:

當您從Network響應中獲取某些內容並希望在視圖中顯示它時(如在TextView中顯示響應消息),您需要返回UI線程。

如果你不這樣做,你將獲得ViewRootImpl$CalledFromWrongThreadException

如何?

  1. 使用AsyncTask時,從onPostExecute()方法更新視圖
  2. 或者run()方法中調用runOnUiThread()方法並更新視圖。

該錯誤是由於在主線程中執行長時間運行操作,您可以使用AsynTaskThread輕鬆解決問題。 您可以檢查此庫AsyncHTTPClient以便更好地處理。

AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {

    @Override
    public void onStart() {
        // Called before a request is started
    }

    @Override
    public void onSuccess(int statusCode, Header[] headers, byte[] response) {
        // Called when response HTTP status is "200 OK"
    }

    @Override
    public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
        // Called when response HTTP status is "4XX" (for example, 401, 403, 404)
    }

    @Override
    public void onRetry(int retryNo) {
        // Called when request is retried
    }
});

關於這個問題已經有很多很好的答案了,但是自從這些答案發布以來,已經出現了許多優秀的圖書館。 這是一種新手指南。

我將介紹幾個用於執行網絡操作的用例和一個或兩個解決方案。

通過HTTP重新安裝

通常Json可以是XML或其他東西

完整的API訪問權限

假設您正在編寫一個應用程序,用戶可以跟踪股票價格,利率和貨幣匯率。 您找到一個類似於以下內容的Json API:

http://api.example.com/stocks                       //ResponseWrapper<String> object containing a list of Srings with ticker symbols
http://api.example.com/stocks/$symbol               //Stock object
http://api.example.com/stocks/$symbol/prices        //PriceHistory<Stock> object
http://api.example.com/currencies                   //ResponseWrapper<String> object containing a list of currency abbreviation
http://api.example.com/currencies/$currency         //Currency object
http://api.example.com/currencies/$id1/values/$id2  //PriceHistory<Currency> object comparing the prices of the first currency (id1) to the second (id2)

廣場改造

對於具有多個端點的API而言,這是一個很好的選擇,並允許您聲明ReST端點,而不必像其他庫(如ion或Volley)那樣單獨編寫它們。 (網站: http://square.github.io/retrofit/http://square.github.io/retrofit/

你如何在財務API中使用它?

的build.gradle

將這些行添加到模塊級別buid.gradle:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit library, current as of September 21, 2017
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' //gson serialization and deserialization support for retrofit, version must match retrofit version

FinancesApi.java

public interface FinancesApi {
    @GET("stocks")
    Call<ResponseWrapper<String>> listStocks();
    @GET("stocks/{symbol}")
    Call<Stock> getStock(@Path("symbol")String tickerSymbol);
    @GET("stocks/{symbol}/prices")
    Call<PriceHistory<Stock>> getPriceHistory(@Path("symbol")String tickerSymbol);

    @GET("currencies")
    Call<ResponseWrapper<String>> listCurrencies();
    @GET("currencies/{symbol}")
    Call<Currency> getCurrency(@Path("symbol")String currencySymbol);
    @GET("currencies/{symbol}/values/{compare_symbol}")
    Call<PriceHistory<Currency>> getComparativeHistory(@Path("symbol")String currency, @Path("compare_symbol")String currencyToPriceAgainst);
}

FinancesApiBuilder

public class FinancesApiBuilder {
    public static FinancesApi build(String baseUrl){
        return new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(FinancesApi.class);
    }
}

FinancesFragment片段

FinancesApi api = FinancesApiBuilder.build("http://api.example.com/"); //trailing '/' required for predictable behavior
api.getStock("INTC").enqueue(new Callback<Stock>(){
    @Override
    public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
        Stock stock = stockCall.body();
        //do something with the stock
    }
    @Override
    public void onResponse(Call<Stock> stockCall, Throwable t){
        //something bad happened
    }
}

如果您的API要求發送API密鑰或其他標頭(如用戶令牌等),Retrofit可以輕鬆實現(請參閱此詳細答案以獲取詳細信息:https://.com/a/42899766/1024412:https://.com/a/42899766/1024412)。

一次關閉ReST API訪問

假設您正在構建一個“情緒天氣”應用程序,該應用程序會查找用戶的GPS位置並檢查該區域的當前溫度並告訴他們心情。這種類型的應用程序不需要聲明API端點; 它只需要能夠訪問一個API端點。

離子

對於這種類型的訪問,這是一個很棒的庫。

請閱讀msysmilu的好答案(https://.com/a/28559884/1024412)

通過HTTP加載圖像

齊射

Volley也可用於ReST API,但由於需要更複雜的設置,我更喜歡使用Square上的Retrofit(http://square.github.io/retrofit/

假設您正在構建社交網絡應用,並希望加載朋友的個人資料照片。

的build.gradle

將此行添加到模塊級別buid.gradle:

implementation 'com.android.volley:volley:1.0.0'

ImageFetch.java

Volley需要比Retrofit更多的設置。您需要創建一個這樣的類來設置RequestQueue,ImageLoader和ImageCache,但它並不太糟糕:

public class ImageFetch {
    private static ImageLoader imageLoader = null;
    private static RequestQueue imageQueue = null;

    public static ImageLoader getImageLoader(Context ctx){
        if(imageLoader == null){
            if(imageQueue == null){
                imageQueue = Volley.newRequestQueue(ctx.getApplicationContext());
            }
            imageLoader = new ImageLoader(imageQueue, new ImageLoader.ImageCache() {
                Map<String, Bitmap> cache = new HashMap<String, Bitmap>();
                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }
                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
        }
        return imageLoader;
    }
}

user_view_dialog.xml

將以下內容添加到佈局xml文件中以添加圖像:

<com.android.volley.toolbox.NetworkImageView
    android:id="@+id/profile_picture"
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    app:srcCompat="@android:drawable/spinner_background"/>

UserViewDialog.java

將以下代碼添加到onCreate方法(Fragment,Activity)或構造函數(Dialog):

NetworkImageView profilePicture = view.findViewById(R.id.profile_picture);
profilePicture.setImageUrl("http://example.com/users/images/profile.jpg", ImageFetch.getImageLoader(getContext());

畢加索

Square的另一個優秀圖書館。請訪問該網站以獲取一些很好的例子:http://square.github.io/picasso/http://square.github.io/picasso/


在Android上,網絡操作無法在主線程上運行。您可以使用Thread,AsyncTask(短期運行任務),Service(長時間運行的任務)來執行網絡操作。


從主(UI)線程訪問網絡資源會導致此異常。使用單獨的線程或AsyncTask訪問網絡資源以避免此問題。


您不能在Android上的UI線程上實現網絡操作。您將不得不使用AsyncTask類來執行網絡相關的操作,如發送API請求,從URL下載圖像等,並使用AsyncTask的回調方法,您可以獲得onPostExecute menthod的結果,您將在UI線程和你可以使用來自Web服務或類似內容的數據填充UI。

示例:假設您要從URL下載圖像:https://www.samplewebsite.com/sampleimage.jpghttps://www.samplewebsite.com/sampleimage.jpg

使用AsyncTask的解決方案:分別是。

    public class MyDownloader extends AsyncTask<String,Void,Bitmap>
    {
        @Override
        protected void onPreExecute() {
            // Show progress dialog
            super.onPreExecute();
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //Populate Ui
            super.onPostExecute(bitmap);
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            // Open URL connection read bitmaps and return form here
            return result;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            // Show progress update
            super.onProgressUpdate(values);
        }


    }
}

注意:不要忘記在Android清單文件中添加Internet權限。它會像魅力一樣工作。 :)


還有另一種解決此問題的非常方便的方法 - 使用rxJava的並發功能。您可以在後台執行任何任務,並以非常方便的方式將結果發佈到主線程,因此這些結果將被傳遞到處理鏈。

第一個經過驗證的答案建議是使用AsynTask。是的,這是一個解決方案,但現在已經過時,因為有新的工具。

String getUrl() {
    return "SomeUrl";
}

private Object makeCallParseResponse(String url) {
    return null;
    //
}

private void processResponse(Object o) {

}

getUrl方法提供URL地址,它將在主線程上執行。

makeCallParseResponse(..) - 做實際工作

processResponse(..) - 將在主線程上處理結果。

異步執行的代碼如下所示:

rx.Observable.defer(new Func0<rx.Observable<String>>() {
    @Override
    public rx.Observable<String> call() {
        return rx.Observable.just(getUrl());
    }
})
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.io())
    .map(new Func1<String, Object>() {
        @Override
        public Object call(final String s) {
            return makeCallParseResponse(s);
        }
    })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Object>() {
        @Override
        public void call(Object o) {
             processResponse(o);
        }
    },
    new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {
            // Process error here, it will be posted on
            // the main thread
        }
    });

與AsyncTask相比,此方法允許任意次數切換調度程序(例如,在一個調度程序上獲取數據並在另一個調度程序上處理這些數據(例如,Scheduler.computation())。您還可以定義自己的調度程序。

要使用此庫,請在build.gradle文件中包含以下行:

   compile 'io.reactivex:rxjava:1.1.5'
   compile 'io.reactivex:rxandroid:1.2.0'

最後一個依賴項包括對.mainThread()調度程序的支持。

rx-java一本很棒的電子書


雖然上面有一個巨大的解決方案池,但沒有人提到com.koushikdutta.ionhttps://github.com/koush/ionhttps://github.com/koush/ion

它也是異步的,使用起來非常簡單

Ion.with(context)
.load("http://example.com/thing.json")
.asJsonObject()
.setCallback(new FutureCallback<JsonObject>() {
   @Override
    public void onCompleted(Exception e, JsonObject result) {
        // do stuff with the result or error
    }
});




thread-exceptions