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


    一、背景
     
    • 复杂的项目:代码复杂度的增加,第三方库的引入,某个Activity or Fragment与其他相关联的类或是方法 或是子模块 。这时候针对某一个Activity进行查找Ui卡顿的问题,然后进行操作是十分困难的!
     
    • 卡顿积累到一定程度造成Activity Not Response,只有在ANR现象下,才能获取到当前堆栈信息
     
    BlockCanary
    ————Android 平台-非侵入式的性能监控组件
    ————针对轻微的UI卡顿及不流畅现象的检查工具
     
     
    1.1、UI卡顿原理——性能优化的大问题之一
     
    最优策略:
    60fps——>16ms/帧(一幅图像) 
    16ms内是否能完成一次操作
     
    准则:尽量保证每次在16ms内处理完单次的所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题
     
    1.2、UI卡顿常见原因:
        1、Ui线程中做轻微耗时操作
                系统为App创建的ActivityThread的作用,将事件分发给合适的view 或是widget;同时是应用和Ui交互的主线程
      
      子线程通知主线程Ui完成可以显示:
    • handler
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable,long)-延时post
        
     2、布局Layout过于复杂,无法再16ms内完成渲染
     3、View过度绘制-负载加剧cpu or gpu
     4、View频繁的触发measure、layout事件,导致在绘制Ui的时候时间加巨,损耗加巨,造成View频繁渲染
     5、内存频繁触发GC过多 ——在同一帧频繁的创建临时变量加剧内存的浪费和渲染的增加
     
    ps:虚拟机在执行Gc垃圾回收的时候,会暂停所有的线程(包括ui线程)。只有当Gc垃圾回收器完成工作才能继续开启线程继续执行工作
    • 尽量减少临时变量的创建———代码优化的小点之一
     
    二、BlockCanary 使用
    2.1、引入依赖:
        implementation'com.github.markzhai:blockcanary-android:1.5.0'
     
    2.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;
        }
            …...
    }
     
    三、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(Messagemsg) {
            //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 流程图
     
     
        1、通过handler.postMessage() 发送消息给主线程
        2、sMainLooper.looper() 通过轮询器不断的轮询MessageQueue中的消息队列
        3、通过Queue.next() 获取需要的消息
        4、计算出调用dispatchMessage()方法中前后的时间差值
        5、通过T2-T1的时间差来判断是否超过设定好的时间差的阀值
        6、如果T2-T1 时间差 > 阀值 ,就dump 出information来定位Ui卡顿
     
        
        7、如果执行完dispatchMessage()后延迟了阀值的0.8倍的话,进行了延迟发送 也会dump出需要的信息(堆栈信息,cpu使用率、内存信息)
     
    三、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);
            }
        });
    }
                                                 
    四、BlockCanary补充知识
        4.1 ANR 定义
     ANR:Application Not responding 程序未响应
        超过预定时间仍然未响应就会造成ANR
        
    监控工具:
        Activity Manager 和WindowManager 进行监控的
     
       4.2 ANR 分类 ***
        1、Service Timeout 
            服务如果在20秒内没有完成执行的话,就会造成ANR
        2、BroadcastQueue Timeout 
            广播如果在10秒内没有完成执行的话,就会造成ANR
        3、InputDispatching Timeout
            输入事件如果超过了5秒钟就会造成ANR
            广播如果在10秒内没有完成执行的话,就会造成ANR
    3、InputDispatching Timeout
        输入事件(触摸屏、点击事件)如果超过了5秒钟就会造成ANR
     
      4.3 ANR造成原因 ***
        1、主线程做了一些耗时操作,比如网络、数据库获取操作等
        2、主线程被其他线程锁住
            主线程所需要的资源在被其他线程所使用中,导致主线程无法获取到该资源而造成主线程的阻塞,进而造成ANR现象
        3、cpu被其他进程占用
            这个进程没有被分配到足够的cpu资源
     
      4.4 ANR 解决措施 ***
       1、主线程读取数据
            主线程禁止从网络获取数据,但是可以从数据库获取数据,虽然未被禁止该操作,但是执行这类操作会造成掉帧现象
     
    Tips : sharePreference 的commit () /apply()
       commit() :同步方法
        apply(): 异步方法
        所以主线程中尽量不要调用 commit()方法,调用同步方法会阻塞主线程,尽量通过apply()方法执行操作
     
            2、不要在BroadCastReceiver 的onReceive()方法中执行耗时操作
            onReceive():也是运行在主线程中的,后台操作。
        通过IntentService()方法执行相关操作
     
       3、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也是一个线程类
        // 简单原理:1、创建一个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 {
               //2、执行完上面的操作会让线程睡眠固定的时间,给线程执行操作留出时间
                Thread.sleep(_timeoutInterval);
            }
            catch (InterruptedException e) {
                _interruptionListener.onInterrupted(e);
                return ;
            }
            //3、线程重新开始运行 检测之前的post的任务是在执行了 两个临时变量是否相等,不相等代表Ui线程没有阻塞
            相等表示 Ui线程没有接收到Post runnable这个消息
           //4、刚才保存的_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 开启线程的弊端
        1、多个耗时任务时就会开多个新线程,开销是非常大的 ,造成很大的性能浪费
        2、如果在线程中执行循环任务,只能通过一个人为的标识位Flag来控制它的停止
        3、没有线程切换的接口
        4、如果从UI线程启动一个子线程,则该线程优先级默认为Default,这样就和Ui线程级别相同了 体现不出开子线程的目的
       通过方法设定线程的优先级,将子线程的优先级将低为后台线程:  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUD);
     
     4.7 线程间通信
     
    Android 多线程编程时的两大原则
        1、不要阻塞Ui线程
     
        2、不要在ui线程之外访问Ui控件
     
    线程间通信的两类:
       1、将任务从工作线程交还到主线程 --更新Ui组件,将结果交回给主线程
    通过handlerMessage()方法将工作线程的结果抛出给主线程
     
    //2、handler机制的looper 轮询 MessageQueue,获取到消息交给handleMessage()
        private Handler handle = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //3、主线程handleMessage(接收的消息)并执行
                super.handleMessage(msg);
            }
        };
    //1、开启一个线程,在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、将任务从主线程分配到工作线程 —耗时操作 分配给工作线程
     
            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();
        //1、创建looper对象
        Looper.prepare();
        synchronized(this) {
           //2、将looper对象赋值给了handleThread的内部变量mLooper获得当前线程的looper
            mLooper = Looper.myLooper();
           //3、唤醒等待线程,通知线程竞争锁(wait :释放锁)
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();//线程初始化操作
       //4、开启线程队列
        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 完成异步执行的
     
    1、首先在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.1、多进程的优点:
            1、解决OOM问题——将耗时的工作放在辅助进程中避免主进程出现OOM
            2、合理利用内存,在适当的时候生成新的进程,在不需要的时候杀掉这个进程
            3、单一进程崩溃不会影响整个app的使用
            4、项目解耦、模块化开发有好处
     
        4.8.2、多进程的缺陷:
         1、每次新进程的创建都会创建一个Application,造成多次创建Application
            解析:根据进程名区分不同的进程,然后进行不同的初始化。不要在Application中进行过度静态变量初始化
           
         2、文件读写潜在的问题
            解析:需要并发访问的文件、本地文件、数据库文件等;多利用接口而避免直接访问文件
        文件锁是基于进程和虚拟机的级别,如果从不同的进程访问一个文件锁,这个锁是失效的!(sharePreference)
            
         3、静态变量和单例模式完全失效
            解析:在进程中间,我们的内存空间是相互独立的。虚拟方法区内的静态变量也是相互独立的,由于静态变量是基于进程的,所以单例模式会失效。在不同进程间访问同一个相同类的静态变量,他的值也不一定相同
            尽量避免在多进程中频繁的使用静态变量
        
        4、线程同步机制完全失效
            解析:Java的同步机制也是由虚拟机进行调度的。两个进程会有两个不同的虚拟机。同步关键字都将没用意义
     
    synchronized 和 voliate 的三大区别 ?
       1、阻塞线程与否
            解析:
    voliate关键字本质上是告诉JVM虚拟机当前的变量在寄存器中的值是不确定的,需要从主存中去获取不会造成线程的阻塞
    synchronized关键字指明的代码块只有当前线程可以访问它的临界区的资源,其他的线程就会被阻塞住
     
        2、使用范围
    voliate关键字 只是修饰变量的
    synchronized关键字不仅可以修饰变量还可以修饰方法
        
        3、原子性-操作不会再分(不会因为多线程造成操作顺序的改变)
    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(){
            //1、只有在instance是空的情况才会创建SingletonLazy对象
            if(null == instance){
                instance = new SingletonLazy();
        }
        return instance;
      }
        //2、加锁只有一个线程可以进入方法体进行对象的创建,实现了延迟加载不会每次加载类的时候都创建对象
        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;
        }
    }
     
     
     
  • 相关阅读:
    南京航空航天大学软件著作权申请办法
    CoDel Test Script
    [编辑中] 免费的Internet流量发生器 | Free Internet Traffic Generators
    关于Java LDAP登录集成
    sonar + ieda实现提交代码前代码校验
    sonar+Jenkins代码覆盖率检测
    定义自己的代码风格CheckStyle简单使用
    HAProxy简单使用
    读取大文件性能测试
    使用HtmlUnit登录百度
  • 原文地址:https://www.cnblogs.com/cold-ice/p/9321986.html
Copyright © 2020-2023  润新知