Android 多線程,線程池,併發處理以及ANR

1. Android 多線程

    1.1 Android中有哪些多線程的方法java

        1) Activity.runOnUiThread(Runnable)android

        2) View.post(Runnable) ;View.postDelay(Runnable , long)數據庫

        3) Handler緩存

        4) AsyncTask網絡

2. Android線程池

Android線程池hreadPoolExecutor是什麼

至關於一個容器,容納的是Thread或者Runable多線程

爲何要使用ThreadPoolExecutor

一、每個線程都是須要CUP去分配的,若是老是須要new thread,那麼會大量耗費CPU資源,致使應用運行變慢,甚至oomapp

二、ThreadPoolExecutor能夠減小銷燬和建立的次數,每一個工做線程能夠重複利用,可執行多個任務dom

三、能夠根據手機cpu核數來控制最大線程數,保證程序合理運行異步

ThreadPoolExecutor的構造方法

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue, 
  ThreadFactory threadFactory) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  threadFactory, defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
  TimeUnit unit, BlockingQueue<Runnable> workQueue,
  RejectedExecutionHandler handler) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  Executors.defaultThreadFactory(), handler);
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
  TimeUnit unit,BlockingQueue<Runnable> workQueue, 
  ThreadFactory threadFactory, RejectedExecutionHandler handler) {
  if (corePoolSize < 0 ||
  maximumPoolSize <= 0 ||
  maximumPoolSize < corePoolSize ||
  keepAliveTime < 0)
  throw new IllegalArgumentException();
  if (workQueue == null || threadFactory == null || handler == null)
  throw new NullPointerException();
  this.corePoolSize = corePoolSize;
  this.maximumPoolSize = maximumPoolSize;
  this.workQueue = workQueue;
  this.keepAliveTime = unit.toNanos(keepAliveTime);
  this.threadFactory = threadFactory;
  this.handler = handler;
}

參數 
public ThreadPoolExecutor( 
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue workQueue, 
ThreadFactory threadFactory) { 
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
threadFactory, defaultHandler); 
}ide

corePoolSize: 核心線程池大小,也就是是線程池中的最小線程數;核心線程在allowCoreThreadTimeout被設置爲true時會超時退出,默認狀況下不會退出;

maximumPoolSize:最大線程池大小,當活動線程數達到這個值,後續任務會被阻塞

keepAliveTime: 
一、線程池中超過corePoolSize數目的非核心線程最大存活時間;閒置時的超時時長,超過這個值後,閒置線程就會被回收,直到線程數量等於corePoolSize。若是allowCoreThreadTimeout設置爲true,則全部線程均會退出直到線程數量爲0。 
二、若是池中當前有多於 corePoolSize 的線程,則這些多出的線程在空閒時間超過 keepAliveTime 時將會終止,這提供了當池處於非活動狀態時減小資源消耗的方法。若是池後來變得更爲活動,則能夠建立新的線程。

unit: 線程池維護線程所容許的空閒時間的單位

workQueue: 
一、線程池所使用的緩衝隊列 
二、執行前用於保持任務的隊列,也就是線程池的緩存隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務

threadFactory:線程工廠,爲線程池提供建立新線程的功能,它是一個接口,只有一個方法:Thread newThread(Runnable r)

RejectedExecutionHandler: 
一、當提交任務數超過maxmumPoolSize+workQueue之和時,任務會交給RejectedExecutionHandler來處理 
二、線程池對拒絕任務的處理策略。通常是隊列已滿或者沒法成功執行任務,這時ThreadPoolExecutor會調用handler的rejectedExecution方法來通知調用者

線程池幾個參數的理解:

1.  好比去火車站買票, 有10個售票窗口, 但只有5個窗口對外開放. 那麼對外開放的5個窗口稱爲核心線程數corePoolSize,而最大線程數maximumPoolSize是10個窗口.

2. 若是5個窗口都被佔用, 那麼後來的人就必須在後面排隊, 但後來售票廳人愈來愈多, 已經人滿爲患, 就相似於線程隊列new LinkedBlockingQueue<Runnable>()已滿.

3. 這時候火車站站長下令, 把剩下的5個窗口也打開, 也就是目前已經有10個窗口同時運行. 後來又來了一批人

4. 10個窗口也處理不過來了, 並且售票廳人已經滿了, 這時候站長就下令封鎖入口,不容許其餘人再進來, 這就是線程異常處理策略.

5. 而線程存活時間keepAliveTime指的是, 容許售票員休息的最長時間, 以此限制售票員偷懶的行時間。休息一下在處理。

ThreadPoolExecutor的執行過程

一個任務經過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是Runnable類型對象的run()方法。

  1. 當線程池小於corePoolSize時,新提交任務將建立一個新線程執行任務,即便此時線程池中存在空閒線程

  2. 當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行

  3. 當提交任務數超過【maximumPoolSize+阻塞隊列大小】時,新提交任務由RejectedExecutionHandler處理 (關於這裏,網上90%以上的人說當任務數>=maximumPoolSize時就會被拒絕,我不知道依據在哪裏,也不知道代碼驗證過沒,通過個人驗證這種說法是不成立的,具體的看下邊日誌分析)

  4. 當線程池中超過corePoolSize線程,空閒時間達到keepAliveTime時,關閉空閒線程

  5. 當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閒時間達到keepAliveTime也將關閉

一個下載音樂的例子

模擬音樂播放器,點擊下載音樂

package com.bourne.android_common.ThreadDemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import com.bourne.android_common.R;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class ThreadPoolExecutorActivity extends AppCompatActivity {
    private final int CORE_POOL_SIZE = 4;//核心線程數
    private final int MAX_POOL_SIZE = 5;//最大線程數
    private final long KEEP_ALIVE_TIME = 10;//空閒線程超時時間
    private ThreadPoolExecutor executorPool;
    private int songIndex = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_pool_executor);
        // 建立線程池
        // 建立一個核心線程數爲四、最大線程數爲5的線程池
        executorPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    }

    /**
     * 點擊下載
     *
     * @param view
     */
    public void begin(View view) {
        songIndex++;
        try {
            executorPool.execute(new WorkerThread("歌曲" + songIndex));
        } catch (Exception e) {
            Log.e("threadtest", "AbortPolicy...已超出規定的線程數量,不能再增長了....");
        }

        // 全部任務已經執行完畢,咱們在監聽一下相關數據
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20 * 1000);
                } catch (Exception e) {

                }
                Li("monitor after");
            }
        }).start();

    }

    private void Li(String mess) {
        Log.i("threadtest", "monitor " + mess
                + " CorePoolSize:" + executorPool.getCorePoolSize()
                + " PoolSize:" + executorPool.getPoolSize()
                + " MaximumPoolSize:" + executorPool.getMaximumPoolSize()
                + " ActiveCount:" + executorPool.getActiveCount()
                + " TaskCount:" + executorPool.getTaskCount()
        );
    }

    public class WorkerThread implements Runnable {
        private String threadName;

        public WorkerThread(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public synchronized void run() {
            boolean flag = true;
            try {
                while (flag) {
                    String tn = Thread.currentThread().getName();
                    //模擬耗時操做
                    Random random = new Random();
                    long time = (random.nextInt(5) + 1) * 1000;
                    Thread.sleep(time);
                    Log.e("threadtest", "線程\"" + tn + "\"耗時了(" + time / 1000 + "秒)下載了第<" + threadName + ">");
                    //下載完了跳出循環
                    flag = false;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getThreadName() {
            return threadName;
        }
    }
}

xml佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:layout_weight="1"
        android:onClick="begin"
        android:text="點擊下載一首歌曲"/>
</LinearLayout>

效果圖: 
界面操做:

 
最終結果: 
這裏寫圖片描述

一、這裏設置了4個核心線程,也就是4個下載線程

private final int CORE_POOL_SIZE = 4;//核心線程數
  •  
/**
     * 點擊下載
     *
     * @param view
     */
    public void begin(View view) {
        songIndex++;
        try {
            executorPool.execute(new WorkerThread("歌曲" + songIndex));
        } catch (Exception e) {
            Log.e("threadtest", "AbortPolicy...已超出規定的線程數量,不能再增長了....");
        }
}

二、耗時操做,下載完畢跳出循環

public class WorkerThread implements Runnable {
        private String threadName;

        public WorkerThread(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public synchronized void run() {
            boolean flag = true;
            try {
                while (flag) {
                    String tn = Thread.currentThread().getName();
                    //模擬耗時操做
                    Random random = new Random();
                    long time = (random.nextInt(5) + 1) * 1000;
                    Thread.sleep(time);
                    Log.e("threadtest", "線程\"" + tn + "\"耗時了(" + time / 1000 + "秒)下載了第<" + threadName + ">");
                    //下載完了跳出循環
                    flag = false;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public String getThreadName() {
            return threadName;
        }
    }

 

4. ANR (Application Not Responding)

 ANR定義:在Android上,若是你的應用程序有一段時間響應不夠靈敏,系統會向用戶顯示一個對話框,這個對話框稱做應用程序無響應(ANR:Application Not Responding)對話框。用戶能夠選擇「等待」而讓程序繼續運行,也能夠選擇「強制關閉」。因此一個流暢的合理的應用程序中不能出現anr,而讓用戶每次都要處理這個對話框。所以,在程序裏對響應性能的設計很重要,這樣系統不會顯示ANR給用戶。

    默認狀況下,在android中Activity的最長執行時間是5秒,BroadcastReceiver的最長執行時間則是10秒。

第一:什麼會引起ANR?

     在Android裏,應用程序的響應性是由Activity Manager和WindowManager系統服務監視的 。當它監測到如下狀況中的一個時,Android就會針對特定的應用程序顯示ANR:

1.在5秒內沒有響應輸入的事件(例如,按鍵按下,屏幕觸摸)
2.BroadcastReceiver在10秒內沒有執行完畢

3.service是20秒

形成以上兩點的緣由有不少,好比在主線程中作了很是耗時的操做,好比說是下載,io異常等。

   

    潛在的耗時操做,例如網絡或數據庫操做,或者高耗時的計算如改變位圖尺寸,應該在子線程裏(或者以數據庫操做爲例,經過異步請求的方式)來完成。然而,不是說你的主線程阻塞在那裏等待子線程的完成——也不是調用 Thread.wait()或是Thread.sleep()。替代的方法是,主線程應該爲子線程提供一個Handler,以便完成時可以提交給主線程。以這種方式設計你的應用程序,將能保證你的主線程保持對輸入的響應性並能避免因爲5秒輸入事件的超時引起的ANR對話框。

 

第二:如何避免ANR?

 

一、運行在主線程裏的任何方法都儘量少作事情。特別是,Activity應該在它的關鍵生命週期方法(如onCreate()和onResume())裏儘量少的去作建立操做。(能夠採用從新開啓子線程的方式,而後使用Handler+Message的方式作一些操做,好比更新主線程中的ui等)

 

二、應用程序應該避免在BroadcastReceiver裏作耗時的操做或計算。但再也不是在子線程裏作這些任務(由於 BroadcastReceiver的生命週期短),替代的是,若是響應Intent廣播須要執行一個耗時的動做的話,應用程序應該啓動一個 Service。(此處須要注意的是能夠在廣播接受者中啓動Service,可是卻不能夠在Service中啓動broadcasereciver,關於緣由後續會有介紹,此處不是本文重點)

 

三、避免在Intent Receiver裏啓動一個Activity,由於它會建立一個新的畫面,並從當前用戶正在運行的程序上搶奪焦點。若是你的應用程序在響應Intent廣 播時須要向用戶展現什麼,你應該使用Notification Manager來實現。

 

dub總結:anr異常也是在程序中本身常常遇到的問題,主要的解決辦法本身最經常使用的就是不要在主線程中作耗時的操做,而應放在子線程中來實現,好比採用Handler+mesage的方式,或者是有時候須要作一些和網絡相互交互的耗時操做就採用asyntask異步任務的方式(它的底層其實Handler+mesage有所區別的是它是線程池)等,在主線程中更新UI。

相關文章
相關標籤/搜索