Android 经常使用开源框架源码解析 系列 (七)BlockCanary 性能优化框架

1、背景
 
  • 复杂的项目:代码复杂度的增长,第三方库的引入,某个Activity or Fragment与其余相关联的类或是方法 或是子模块 。这时候针对某一个Activity进行查找Ui卡顿的问题,而后进行操做是十分困难的!
 
  • 卡顿积累到必定程度形成Activity Not Response,只有在ANR现象下,才能获取到当前堆栈信息
 
BlockCanary
————Android 平台-非侵入式的性能监控组件
————针对轻微的UI卡顿及不流畅现象的检查工具
 
 
1.一、UI卡顿原理——性能优化的大问题之一
 
最优策略:
60fps——>16ms/帧(一幅图像) 
16ms内是否能完成一次操做
 
准则:尽可能保证每次在16ms内处理完单次的全部的CPU与GPU计算、绘制、渲染等操做,不然会形成丢帧卡顿问题
 
1.二、UI卡顿常见缘由:
    一、Ui线程中作轻微耗时操做
            系统为App建立的ActivityThread的做用,将事件分发给合适的view 或是widget;同时是应用和Ui交互的主线程
  
  子线程通知主线程Ui完成能够显示:
  • handler
  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)-延时post
    
 二、布局Layout过于复杂,没法再16ms内完成渲染
 三、View过分绘制-负载加重cpu or gpu
 四、View频繁的触发measure、layout事件,致使在绘制Ui的时候时间加巨,损耗加巨,形成View频繁渲染
 五、内存频繁触发GC过多 ——在同一帧频繁的建立临时变量加重内存的浪费和渲染的增长
 
ps:虚拟机在执行Gc垃圾回收的时候,会暂停全部的线程(包括ui线程)。只有当Gc垃圾回收器完成工做才能继续开启线程继续执行工做
  • 尽可能减小临时变量的建立———代码优化的小点之一
 
2、BlockCanary 使用
2.一、引入依赖:
    implementation'com.github.markzhai:blockcanary-android:1.5.0'
 
2.二、在代码中注册blockCanary:
 
 (1)、application代码中初始化:
@Override
public void onCreate() {
    super.onCreate();
    //第一个参数上下文,第二个参数是blockcanary建立的本身的上下文
   (2)、BlockCanary.install(this, new AppBlockContenxt() ).start();
}
//   (3)、blockContext 特有的上下文的建立
public class AppBlockContext extends BlockCanaryContext {
    //实现各类上下文,包括应用标识符,用户uid ,网络类型,卡慢判断阀值,Log保存位置等内容
        …...
    //1000ms,事件处理时间的阀值,能够经过修改这个阀值来修改超时阀值
    public int provideBlockThreshold() {
        return 1000;
    }
        …...
}
 
3、BlockCanary 核心实现原理                                         
 
BlockCanary 核心实现原理 :离不开主线程 ActivityThread +handler+looper轮询器
 
androidxref.com 在线查看源码网站
 
每一个App只有一个主线程就是ActivityThread线程。
 
3.1 ActivityThread源码:
在官方的ActivityThread的源码中,能够看到:
publicstaticvoidmain(String[] args) {
    …
    Looper.prepareMainLooper(); 
  • //主线程下建立好MainLooper后,关联一个消息队列MessageQueue;
  • MainLooper就会在生命周期内不断的进行轮询操做,经过Looper获取到MessageQueue中的message,而后通知主线程去更新Ui
    ...
}
在prepareMainLooper()中:
/**
*经过MyLooper()函数建立一个主线程的looper
  • 不论一共有多少个子线程,主线程只会有这一个looper,同理不论建立多少个handler最后都会关联到这个looper上
*/
publicstaticvoidprepareMainLooper() {
     prepare(false);
     synchronized(Looper.class) {
         if(sMainLooper!= null) {
             thrownewIllegalStateException("The main Looper has already been prepared.");
           }
             sMainLooper= myLooper();
           }
        }
在Looper中是如何实现消息的分发的呢?
 
在Looper.class() 中
    msg.target.dispatchMessage(msg);//分发message ;msg.target 实际上就是handler
         ...
}
    /**
        * Handle system messages here.
        核心处理消息方法
     */
 public  void   dispatchMessage( Message msg) {
        //if :在这里这个callback 实际上就是runnable,因此handleCallback实际上就是执行了runnbale中的run()函数来执行子线程
        if(msg.callback!= null) {
             handleCallback(msg);
        } else{
             if(mCallback!= null) {
        //else :handler经过sendMessage()方式来投递message 到messageQueue中
                 if(mCallback.handleMessage(msg)) {
                        return;
                   }
               }
     //调用该方法来处理消息 ,不论如何最后的回调必定是发生在Ui线程上
             handleMessage(msg);
        }
   }
ps:若是Ui卡顿颇有多是由于在dispatchMessage()这个函数里执行了卡顿的耗时操做
 
思考:blockCanary 是如何经过android中dispatchMessage()原理实现打印的呢?
 
final Printer  logging= me.mLogging;
 if (logging!= null) {
 logging.println(">>>>> Dispatching to "+ msg.target+ " "+
 msg.callback+ ": "+ msg.what);
         }
    …
msg.target.dispatchMessage(msg);
    ...
if (logging!= null) {
 logging.println("<<<<< Finished to "+ msg.target+ " "+ msg.callback);
          }
解析:blockCanary 利用了handler原理在dispatchMessage()的上下方分别打印方法执行的时间,而后根据上下两个时间差,来判断dispatchMessage()中是否产生了耗时的操做,也就是这个dispatchMessage():是否有Ui卡顿;若是有Ui卡顿上下两个值计算的阀值就是配置的阀值,若是超过这个阀值,就能够dump出Ui卡顿的信息。经过堆栈信息来定位卡顿问题
 
3.2 blockCanary 流程图
 
 
    一、经过handler.postMessage() 发送消息给主线程
    二、sMainLooper.looper() 经过轮询器不断的轮询MessageQueue中的消息队列
    三、经过Queue.next() 获取须要的消息
    四、计算出调用dispatchMessage()方法中先后的时间差值
    五、经过T2-T1的时间差来判断是否超过设定好的时间差的阀值
    六、若是T2-T1 时间差 > 阀值 ,就dump 出information来定位Ui卡顿
 
    
    七、若是执行完dispatchMessage()后延迟了阀值的0.8倍的话,进行了延迟发送 也会dump出须要的信息(堆栈信息,cpu使用率、内存信息)
 
3、BlockCanary 源码
BlockCanary.install(this, new AppBlockContenxt()).start();
 
install():
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
   //将context,blockCanaryContext 赋值给BlockCanaryContext
    BlockCanaryContext.init(context, blockCanaryContext);
   //是否开启或是关闭展现通知栏的界面
    setEnabled(context, DisplayActivity.class,         BlockCanaryContext.get().displayNotification());
    return get();
}
//决定 通知栏 开启or 关闭 的策略:BlockCanaryContext.get().displayNotification()
ps:displayNotification在debug 和test版本都是返回true,只有在release版本才返回fasle,也就是说 displayNotification 是不会展现的
 
get():经过get()单例模式生成BlockCanary的实例
public static BlockCanary get() {
    if (sInstance == null) {
        synchronized (BlockCanary.class) {
            if (sInstance == null) {
                sInstance = new BlockCanary();
            }
        }
    }
    return sInstance;
}
 
BlockCanary() 构造函数的实现:   //BlockCanary()内部类就是blockCanary核心的实现
private BlockCanary() {
    BlockCanaryInternals.setContext(BlockCanaryContext.get());
    mBlockCanaryCore = BlockCanaryInternals.getInstance();
   //传入BlockCanaryContext.get()的上下文,只有开启通知栏的时候才会展开下面的BlockInterceptor拦截器
    mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
    if (!BlockCanaryContext.get().displayNotification()) {
        return;
    }
   //传入BlockCanary内部实现的,来展现DisplayService()
    mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
 
BlockCanaryInternals():
public BlockCanaryInternals() {
    //dump出线程的dump信息,传入参数主线程,looper.getMainLooper().getThread()
    stackSampler= new StackSampler(
            Looper.getMainLooper().getThread(),
            sContext.provideDumpInterval());
   //dump出cpu有关信息
    cpuSampler= new CpuSampler(sContext.provideDumpInterval());
    //内部建立一个LooperMonitor,在该方法中控制时间差
    setMonitor(new  LooperMonitor(new LooperMonitor.BlockListener() {
       //在该方法内打印:主线程调用栈、cpu使用状况、内存状况
        @Override
        public void onBlockEvent(long realTimeStart, long realTimeEnd,
                                 long threadTimeStart, long threadTimeEnd) {
            // Get recent thread-stack entries and cpu usage
            ArrayList<String> threadStackEntries = stackSampler
                    .getThreadStackEntries(realTimeStart, realTimeEnd);
            if (!threadStackEntries.isEmpty()) {
                BlockInfo blockInfo = BlockInfo.newInstance()
                        .setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
                        .setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
                        .setRecentCpuRate(cpuSampler.getCpuRateInfo())
                        .setThreadStackEntries(threadStackEntries)
                        .flushString();
                LogWriter.save(blockInfo.toString());
 
                if (mInterceptorChain.size() != 0) {
                    for (BlockInterceptor interceptor : mInterceptorChain) {
                        interceptor.onBlock(getContext().provideContext(), blockInfo);
                    }
                }
            }
        }
    }, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
            //删除日志,默认状况下日志保存2天
    LogWriter.cleanObsolete();
}
 
start():
public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
       //获取主线程looper;再调用主线程的setMessageLogging()进行时间打点
        ps:mBlockCanaryCore.monitor 在BlockCanaryInternals()中建立
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}
monitor():
class LooperMonitor implements Printer {
    
    //时间打点方法
@Override
public void println(String x) {
    if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
        return;
    }
 
    if (!mPrintingStarted) {
       //获取系统时间的时间戳 mStartTimestamp
        mStartTimestamp = System.currentTimeMillis();
       //获取当前线程运行的时间mStartThreadTimestamp,当前线程处于运行状态的总时间
    ps:线程中的sleep、wait时间不会记录在这个总时间内
        mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
        mPrintingStarted = true;
        startDump();//打印开始时间的堆栈信息
    } else {        //在dispatchMessage()以后进行以下操做
        final long endTime = System.currentTimeMillis();
        mPrintingStarted = false;
        if (isBlock(endTime)) { //产生Ui卡顿现象
            notifyBlockEvent(endTime);
        }
        stopDump();//打印结束时间的堆栈信息
    }
}
//经过BlockCanaryInternals()方法分别打印stackSampler和cpuSampler
startDump():
private void startDump() {
    if (null != BlockCanaryInternals.getInstance().stackSampler) {
        BlockCanaryInternals.getInstance().stackSampler.start();
    }
    if (null != BlockCanaryInternals.getInstance().cpuSampler) {
        BlockCanaryInternals.getInstance().cpuSampler.start();
    }
}
.stackSampler.start():
public void start() {
    if (mShouldSample.get()) {
        return;
    }
    mShouldSample.set(true);
 
    HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
   //调用handler的postDelayed方法传递一个runnable
    HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
            BlockCanaryInternals.getInstance().getSampleDelay());
}
该runnable 定义在AbstractSampler 抽象类中(cpuSampler、stackSampler)
private Runnable mRunnable= new Runnable() {
    @Override
    public void run() {
        doSample();
        if (mShouldSample.get()) {
            HandlerThreadFactory.getTimerThreadHandler()
                    .postDelayed(mRunnable, mSampleInterval);
        }
    }
};
     doSample(); 抽象方法,意味着stackSampler和 cpuSampler会有不一样的实现
abstract void doSample();
 
stackSampler():
@Override
protected void doSample() {
    StringBuilder stringBuilder = new StringBuilder();
       //获取到当前线程的调用栈信息
    for (StackTraceElement stackTraceElement :mCurrentThread.getStackTrace() ) {
        stringBuilder
                .append(stackTraceElement.toString())
                .append(BlockInfo.SEPARATOR);
    }
 
    synchronized (sStackMap) {
        if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
            sStackMap.remove(sStackMap.keySet().iterator().next());
        }
        //在同步代码块中,以当前时间戳为key,put放入到StackMap这个HashMap中
       sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
    }
}
private static final LinkedHashMap<Long, String> sStackMap = new LinkedHashMap<>();
思考:sStackMap为何被定义成LinkedHashMap?
 
LinkedHashMap 和HashMap最大的区别?
  • LinkedHashMap可以记录entry的插入顺序!,插入的顺序是已知的
  • HashMap不能记录顺序,未知的
 
cpuSampler():
@Override
protected void doSample() {
    BufferedReader cpuReader = null;
    BufferedReader pidReader = null;
 
    try {
        //经过bufferReader读取 /proc 下的cpu文件
        cpuReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/stat")), BUFFER_SIZE);
          ...
        if (mPid == 0) {
            mPid = android.os.Process.myPid();
        }
            //经过bufferReader读取 /proc 下的内存文件
        pidReader = new BufferedReader(new InputStreamReader(
                new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
        String pidCpuRate = pidReader.readLine();
        if (pidCpuRate == null) {
            pidCpuRate = "";
        }
        parse(cpuRate, pidCpuRate);
           ...
    }
    //T2-T1 时间 是否 > 设定好的blockCanary 阀值时间的最大值
    
isBlock():
private boolean isBlock(long endTime) {
    //若是大于该mBlockThresholdMillis 返回 true 说明存在卡顿状况,有可能产生阻塞现象
    return endTime - mStartTimestamp > mBlockThresholdMillis;
}
notifyBlockEvent():
private void notifyBlockEvent(final long endTime) {
    final long startTime = mStartTimestamp;
    final long startThreadTime = mStartThreadTimestamp;
    final long endThreadTime = SystemClock.currentThreadTimeMillis();
    HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
        @Override
        public void run() {
            //调用监听事件,它的回调方法在BlockCanaryInternals().setMonitor()回调方法中
           mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
        }
    });
}
                                             
4、BlockCanary补充知识
    4.1 ANR 定义
 ANR:Application Not responding 程序未响应
    超过预约时间仍然未响应就会形成ANR
    
监控工具:
    Activity Manager 和WindowManager 进行监控的
 
   4.2 ANR 分类 ***
    一、Service Timeout 
        服务若是在20秒内没有完成执行的话,就会形成ANR
    二、BroadcastQueue Timeout 
        广播若是在10秒内没有完成执行的话,就会形成ANR
    三、InputDispatching Timeout
        输入事件若是超过了5秒钟就会形成ANR
        广播若是在10秒内没有完成执行的话,就会形成ANR
三、InputDispatching Timeout
    输入事件(触摸屏、点击事件)若是超过了5秒钟就会形成ANR
 
  4.3 ANR形成缘由 ***
    一、主线程作了一些耗时操做,好比网络、数据库获取操做等
    二、主线程被其余线程锁住
        主线程所须要的资源在被其余线程所使用中,致使主线程没法获取到该资源而形成主线程的阻塞,进而形成ANR现象
    三、cpu被其余进程占用
        这个进程没有被分配到足够的cpu资源
 
  4.4 ANR 解决措施 ***
   一、主线程读取数据
        主线程禁止从网络获取数据,可是能够从数据库获取数据,虽然未被禁止该操做,可是执行这类操做会形成掉帧现象
 
Tips : sharePreference 的commit () /apply()
   commit() :同步方法
    apply(): 异步方法
    因此主线程中尽可能不要调用 commit()方法,调用同步方法会阻塞主线程,尽可能经过apply()方法执行操做
 
        二、不要在BroadCastReceiver 的onReceive()方法中执行耗时操做
        onReceive():也是运行在主线程中的,后台操做。
    经过IntentService()方法执行相关操做
 
   三、Activity的生命周期函数中都不该该有太耗时的操做
        该生命周期函数大多数执行于主线程中,及时是Service 服务或是 内容提供者ContentProvider也不要在onCreate()中执行耗时操做
 
   4.5 ANR 第三方监控工具 watchdog 检测anr工具
    在linux 内核下,当Watchdog 启动后,便设定了一个定时器,当出现故障时候,经过会让Android系统重启。因为这种机制的存在,常常会出现一些system_server 进程被watchdog杀掉而发生手机重启的问题。
    
    4.5.1 watchdog 初始化
Android 的watchdog 是一个单例线程 ,在System server时候就会初始化watchdog 。在watchdog初始化化时候会构建不少 HandlerChecker 
    大体能够分为两类:
Monitor Checker,用于检查是Monitor对象可能发生的死锁, AMS, PKMS, WMS等核心的系统服务都是Monitor对象。
Looper Checker,用于检查线程的消息队列是否长时间处于工做状态。Watchdog自身的消息队列,Ui, Io, Display这些全局的消息队列都是被检查的对象。此外,一些重要的线程的消息队列,也会加入到Looper Checker中,譬如AMS, PKMS,这些是在对应的对象初始化时加入的。
 private Watchdog(){
    …
    mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                                        “foreground thread”,DEFAULT_TIMEOUT);
    mHandlerCheckers.add(mMonitorChecker);
    mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                                        “main thread”,DEFAULT_TIMEOUT));
 
    mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                                        “ui thread”,DEFAULT_TIMEOUT));
    mHandlerCheckers.add(new HandlerChecker(IOThread.getHandler(),
                                        “i/o thread”,DEFAULT_TIMEOUT)):
    mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                                        “display thread”,DEFAULT_TIMEOUT));
    …
}
    两类 HandlerChecker的侧重点不一样,Monitor Checker预警咱们不能长时间持有核心系统服务的对象锁,不然会阻塞不少函数的运行;Looper Checker 预警我妈不能长时间的霸占消息队列,不然其余消息将得不处处理。
    这两类都会致使系统卡住 ANR
 
public class ANRWatchDog extends Thread 继承自Thread类 代表气势ANRWatchDog也是一个线程类
    // 简单原理:一、建立一个ANR线程,不断的向Ui线程经过handler post一个runnable任务
@Override
public void run() {
    setName("|ANR-WatchDog|");
 
    int lastTick;
    int lastIgnored = -1;
    while (!isInterrupted()) {
        lastTick =_tick; //保存_tick成员变量
        _uiHandler.post(_ticker);
            ps1:handler在构造方法中传入了一个Looper.getMainLooper()主线程looper
        this._uiHandler = new Handler(Looper.getMainLooper()); 因此这个Ui looper 很显然
关联的主线程 ,因此能够看出经过这个_uiHandler会将任务发送给主线程!
            ps2:_tick在构造方法中的runnable()函数中 的run方法里每次会给_tick 计数 +1 是
                ANRWatchDog.this._tick = (ANRWatchDog.this._tick +1) %2147483647 ;
        try {
           //二、执行完上面的操做会让线程睡眠固定的时间,给线程执行操做留出时间
            Thread.sleep(_timeoutInterval);
        }
        catch (InterruptedException e) {
            _interruptionListener.onInterrupted(e);
            return ;
        }
        //三、线程从新开始运行 检测以前的post的任务是在执行了 两个临时变量是否相等,不相等表明Ui线程没有阻塞
        相等表示 Ui线程没有接收到Post runnable这个消息
       //四、刚才保存的_tick变量是否等于 刚才开启子线程当中进行run()+1 是否不等于保存的变量。经过对比来查看post runnable 是否已经发送到了主线程,主线程是否已经执行了该消息,执行后_tick != lastTick
        // If the main thread has not handled _ticker, it is blocked. ANR.
        
        if (_tick == lastTick) {
            if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
                if (_tick != lastIgnored)
                    Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
                lastIgnored = _tick;
                continue ;
            }
            //提高用户
            ANRError error;
            if (_namePrefix != null)
                error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
            else
                error = ANRError.NewMainOnly();
            _anrListener.onAppNotResponding(error);
            return;
        }
    }
}
 
 4.6 newThread 
Android 系统中最简单开启一个线程操做的机制
    public void test (){
      new Thread(){
        @Override
        public void run(){
            super.run():
        }
    }.start()://start()线程处于就绪状态,一旦调用start(),run方法里就会执行到底没法中途取消
区别:
start() :启动线程,真正实现了多线程的运行,无需等待其中的run()方法执行完,能够直接执行下面的代码。就绪了一个线程并无直接就开始运行,告诉cpu能够运行状态
run() :代表将该方法做为普同方法使用,想要执行run()方法后面的方法必定要等待里面的方法体执行完才能进行。因此也就是代表调用run()方法还不是多线程。
 
new Thread 在Android 开启线程的弊端
    一、多个耗时任务时就会开多个新线程,开销是很是大的 ,形成很大的性能浪费
    二、若是在线程中执行循环任务,只能经过一我的为的标识位Flag来控制它的中止
    三、没有线程切换的接口
    四、若是从UI线程启动一个子线程,则该线程优先级默认为Default,这样就和Ui线程级别相同了 体现不出开子线程的目的
   经过方法设定线程的优先级,将子线程的优先级将低为后台线程:  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUD);
 
 4.7 线程间通讯
 
Android 多线程编程时的两大原则
    一、不要阻塞Ui线程
 
    二、不要在ui线程以外访问Ui控件
 
线程间通讯的两类:
   一、将任务从工做线程交还到主线程 --更新Ui组件,将结果交回给主线程
经过handlerMessage()方法将工做线程的结果抛出给主线程
 
//二、handler机制的looper 轮询 MessageQueue,获取到消息交给handleMessage()
    private Handler handle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //三、主线程handleMessage(接收的消息)并执行
            super.handleMessage(msg);
        }
    };
//一、开启一个线程,在run方法中sendMessage 发送消息。
public void handlerTest() {
    new Thread() {
        @Override
        public void run() {
            super.run();
            handle.sendEmptyMessage(0);
        }
    }.start();
 
在子线程中建立一个handler
new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        //在子线程中建立一个handler 必须要先调用Looper.prepare(),建立一个给这个Handler使用的looper。让这个handler和建立好的looper关联起来。这个handler就是处理子线程消息的。
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}.start();
 
(1)、final Runnable runnable = new Runnable() {
            @Override
            public void run() {
 
            }
        };
   (2)、new Thread() {
    @Override
    public void run() {
        super.run();
    //在子线程中使用handle.post(runnable)并无开启了一个新的线程!
    ps:只是让run()方法中的代码抛到与handle相关的线程中去执行
        handle.post(runnable);
    }
}.start();
    //更新Ui的代码建立在runnable当中,只有是若当前线程是Ui线程时才会被当即执行
   (3)、 Activity.this.runOnUiThread(runnable);
    (4)、AsyncTask 内部经过线程池管理线程,经过handler切换Ui 线程和子线程
缺陷:AsyncTask 会持有当前Activity的引用,在使用的时候要把AsyncTask声明为静态static,在AsyncTask内部持有外部Activity的弱引用,预防内存泄漏
    class MyAsyncTask extends AsyncTask<Void,Void,Void>{
        @Override 
            protected void doInBackground(Void ..params){
            //进行耗时操做,由于优先级时background因此不会阻塞Ui线程
        3.0之后默认api设置为串行的缘由:
            当一个进程中开启多个AsyncTask的时候,它会使用同一个线程池执行任务,因此又多个AsyncTask一块儿并行执行的话,并且要在DIBG中访问相同的资源,这时候就有可能出现数据不安全的状况。设计成串行就不会有多线程数据不安全的问题。
            }
        @Override
            protected void onPostExecute(Void voida){
            }
        }
 
     二、将任务从主线程分配到工做线程 —耗时操做 分配给工做线程
 
        2.1 Thread /Runnable
缺陷: Runnable 做为匿名内部类会持有外部类的引用 ,线程执行完前这个引用就会一直持有着,致使该Activity没法被正常回收 ,进而形成内存泄漏。因此不建议使用这类方法开启子线程
 
        2.2 HandlerThread
   继承自Thread 类,适用于单线程或是异步队列场景,耗时很少不会产生较大阻塞的状况好比io流读写操做,并不适合于进行网络数据的获取!!!
优势:
  • 有本身内部的Looper对象,
  • 经过Looper().prepare()能够初始化looper对象
  • 经过Looper().loop()开启looper循环
  • HandlerThread的looper对象传递给Handler对象,而后在handleMessage()方法中执行异步任务
 
ps:不管是Thread 仍是handlerThread只有开启了start()才能表示这个线程是启动的
 
handlerThread源码:
根据需求设置线程的优先级:
    public HandlerThread(String name(线程名称), int priority(线程优先级)){
        super(name);
    mPriority = priority;
    }
 
线程优先级:-20~19 优先级高的cpn资源得到更多,最高值是-20 ,正19是优先级最低的
 
//在run()中建立一个Looper对象;经过  Looper.prepare()和Looper.loop()构造一个循环的线程
@Override
public void run() {
    mTid = Process.myTid();
    //一、建立looper对象
    Looper.prepare();
    synchronized(this) {
       //二、将looper对象赋值给了handleThread的内部变量mLooper得到当前线程的looper
        mLooper = Looper.myLooper();
       //三、唤醒等待线程,通知线程竞争锁(wait :释放锁)
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();//线程初始化操做
   //四、开启线程队列
    Looper.loop();
    mTid = -1;
}
//在Ui线程中调用getLooper()
public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
       //同步代码块:获取当前子线程HandleThread所关联的这个looper对象的实例
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
       //该线程是否存活,没存活返回null
        while (isAlive() && mLooper == null) {
            try {
             //进入到阻塞阶段,直到前面的同步代码块中looper对象建立成功,并调用notifyAll()唤醒该等待线程,而后才会返回mLooper这个对象
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
ps:若是不使用wait() 和notifyAll()这套等待唤醒机制,就没法保证在Ui线程中调用的getLooper()方法,调用的时候这个looper有可能已经被建立了。有可能面临同步的问题。
 
思考:如何退出HandleThread的循环线程呢?
a、quit()
    public boolean quit(){
        Looper looper = getLooper();
        if(looper !=null){
            //清空全部MessageQueue的消息
           looper.quit():
            return true;
        }
        return false;
    }
 
b、quitSafely()
    public boolean quitSafely(){
        Looper looper = getLooper();
        //只会清空MessageQueue中全部的延迟消息,将全部的非延迟消息分发出去
        if(looper!=null){
            looper.quitSafely();
            return true;
        }
            return false;
    }
 
        2.3 IntentService
  • IntentService 是Service类的子类,拥有service全部的生命周期的方法
  • 会单独开启一个线程HandlerThread 来处理全部的Intent请求所对应的任务
  • 当IntentService处理完全部的任务后,它会在适当的时候自动结束服务
 
IntentService源码
 内部实现是经过HandleThread 完成异步执行的
 
一、首先在onCreate()方法中:
//创建一个循环的工做线程
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        //mServiceLooper、mServiceHandler进行数据的读取  
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
 
思考:为何要用volatile 修饰 mServiceLooper 和mServiceHandler?
 
解析:保证每一次looper 和ServiceHandler它的读取都是从主存中读取,而不是从各个线程中的缓存去读取。保证了两个数据的安全性。
 
//handler接收到消息要回调的,onHandleIntent这个是在工做线程执行的
protected abstract void onHandleIntent(@Nullable Intent intent);
 
 
 4.8 多进程的优势 与缺陷
 
    4.8.一、多进程的优势:
        一、解决OOM问题——将耗时的工做放在辅助进程中避免主进程出现OOM
        二、合理利用内存,在适当的时候生成新的进程,在不须要的时候杀掉这个进程
        三、单一进程崩溃不会影响整个app的使用
        四、项目解耦、模块化开发有好处
 
    4.8.二、多进程的缺陷:
     一、每次新进程的建立都会建立一个Application,形成屡次建立Application
        解析:根据进程名区分不一样的进程,而后进行不一样的初始化。不要在Application中进行过分静态变量初始化
       
     二、文件读写潜在的问题
        解析:须要并发访问的文件、本地文件、数据库文件等;多利用接口而避免直接访问文件
    文件锁是基于进程和虚拟机的级别,若是从不一样的进程访问一个文件锁,这个锁是失效的!(sharePreference)
        
     三、静态变量和单例模式彻底失效
        解析:在进程中间,咱们的内存空间是相互独立的。虚拟方法区内的静态变量也是相互独立的,因为静态变量是基于进程的,因此单例模式会失效。在不一样进程间访问同一个相同类的静态变量,他的值也不必定相同
        尽可能避免在多进程中频繁的使用静态变量
    
    四、线程同步机制彻底失效
        解析:Java的同步机制也是由虚拟机进行调度的。两个进程会有两个不一样的虚拟机。同步关键字都将没用意义
 
synchronized 和 voliate 的三大区别 ?
   一、阻塞线程与否
        解析:
voliate关键字本质上是告诉JVM虚拟机当前的变量在寄存器中的值是不肯定的,须要从主存中去获取不会形成线程的阻塞
synchronized关键字指明的代码块只有当前线程能够访问它的临界区的资源,其余的线程就会被阻塞住
 
    二、使用范围
voliate关键字 只是修饰变量的
synchronized关键字不只能够修饰变量还能够修饰方法
    
    三、原子性-操做不会再分(不会由于多线程形成操做顺序的改变)
voliate关键字不具有原子性
synchronized关键字能够保证变量的原子性
 
 4.9  voliate关键字和单例写法
    单例模式:饿汉、懒汉、线程安全的 分析其中的问题
 
饿汉 单例模式:构造函数是私有的   
缺陷:消耗不少内存,无论是否已经实例化过instance;适合占用内存比较小的单例
 
public class Singleton{
    private static Singleton instance = new Singleton();
    //其余类不能经过构造函数实例化Singleton类
   private Singleton(){}
    //提供一个静态的公共的获取类的方法
    public static Singleton getInstance(){
        return instance;
    }
}
ps:无论instance是否建立完成,在加载类的时候都回去建立这个对象。
 
占用内存大的时候就衍生出了,相比饿汉单例模式消耗的资源更少
缺陷:并无考虑多线程安全的问题,屡次调用带有同步锁的代码块累积的性能损害就愈来愈大
懒汉单例模式
public class SingletonLazy {
    private static SingletonLazy instance;
    private SingletonLazy(){}
    public static SingletonLazy getInstance(){
        //一、只有在instance是空的状况才会建立SingletonLazy对象
        if(null == instance){
            instance = new SingletonLazy();
    }
    return instance;
  }
    //二、加锁只有一个线程能够进入方法体进行对象的建立,实现了延迟加载不会每次加载类的时候都建立对象
    public static synchronized SingletonLazy getInstance1(){
        if(null == instance) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}
ps:经过synchronized关键字修饰的对象虽然会线程安全,可是会消耗更多的资源。
 
双重效验锁单例模式:
优势:解决了并发问题
缺陷:指令冲排序优化问题,致使初始化的时候它instance获取的地址是不肯定的
也就是说这个instance有可能从单个缓存中获取,每个线程的缓存是不同的,就会形成静态的这个
instance不一致 形成单例的失灵
 
//双重instance 判断
 public class SingletonDouble{
    private static SingletonDouble instance = null;
    private SingletonDouble{}
   
    public static SingletonDouble getInstance(){
        if(instance == null){
        //大部分状况下不须要进入同步代码块中
            synchronized (SingletonDouble.class){
                if(instance ==null){
                    instance = new SingletonDouble():
                }
            }
        }
        return instance;
      }
}
ps:若是同时两个线程都走到  if(instance == null){的判断,那么它会认为单例对象没有被建立,而后两个线程会同时进入同步代码块中。若是这时候判断对象为空的话,两个线程会同时建立单例对象
 
voliate关键字的单例模式
为了解决第三个的问题 出现了voliate关键字的使用
public class SingletonVoliate{
    //当单例对象被修饰成voliate后,每一次instance内存中的读取都会从主内存中获取,而不会从缓存中获取,这样就解决了双重效验锁单例模式的缺陷
    private static volatile SingletonVoliate instance =null;
    private SingletonVoliate(){}
    public static SingletonVoliate getInstance(){
      if(instance ==null){
        synchronized(SingletonVoliate.class){
            if(instance ==null){
                instance = new SingletonVoliate():
            }
         }
      }
        return instance;
    }
}
相关文章
相关标签/搜索