精华内容
下载资源
问答
  • [img=https://img-bbs.csdn.net/upload/202009/27/1601198758_498292.png][/img]
  • 前言 Android的消息机制主要是指Handler的运行机制,对于大家来说Handler已经是轻车熟路了,可是真的掌握了Handler?本文主要通过几个问题围绕着Handler展开深入... 主线程的消息循环机制是什么(死循环如何处理其它事

    前言

    Android的消息机制主要是指Handler的运行机制,对于大家来说Handler已经是轻车熟路了,可是真的掌握了Handler?本文主要通过几个问题围绕着Handler展开深入并拓展的了解。

    站在巨人的肩膀上会看的更远。大家有兴趣的也可以到Gityuan的博客上多了解了解,全部都是干货。而且他写的东西比较权威,毕竟也是小米系统工程师的骨干成员。

    Questions

    1. Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?

    2. 主线程的消息循环机制是什么(死循环如何处理其它事务)?

    3. ActivityThread 的动力是什么?(ActivityThread执行Looper的线程是什么)

    4. Handler 是如何能够线程切换,发送Message的?(线程间通讯)

    5. 子线程有哪些更新UI的方法。

    6. 子线程中Toast,showDialog,的方法。(和子线程不能更新UI有关吗)

    7. 如何处理Handler 使用不当导致的内存泄露?

    8. Looper 死循环为什么不会导致应用卡死?

      线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

    首先我们看一段代码

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("qdx", "step 0 ");
                Looper.prepare();
    
                Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();
    
                Log.e("qdx", "step 1 ");
                Looper.loop();
    
                Log.e("qdx", "step 2 ");
    
            }
        }).start();
    

    我们知道Looper.loop();里面维护了一个死循环方法,所以按照理论,上述代码执行的应该是 step 0 –>step 1 也就是说循环在Looper.prepare();与Looper.loop();之间。

    在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待(阻塞)状态,而如果退出Looper以后,这个线程就会立刻(执行所有方法并)终止,因此建议不需要的时候终止Looper。

    执行结果也正如我们所说,这时候如果了解了ActivityThread,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环。

     

    public static void main(String[] args) {
        Looper.prepareMainLooper();//创建Looper和MessageQueue对象,用于处理主线程的消息
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);//建立Binder通道 (创建新线程)
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
    
        //如果能执行下面方法,说明应用崩溃或者是退出了...
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    那么回到我们的问题上,这个死循环会不会导致应用卡死,即使不会的话,它会慢慢的消耗越来越多的资源吗?

    对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

    主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 Gityuan–Handler(Native层)

    2. 主线程的消息循环机制是什么?

    事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:
     

    
    ```
    public static void main(String[] args) {
    //创建Looper和MessageQueue对象,用于处理主线程的消息
     Looper.prepareMainLooper();
    
     //创建ActivityThread对象
     ActivityThread thread = new ActivityThread(); 
    
     //建立Binder通道 (创建新线程)
     thread.attach(false);
    
     Looper.loop(); //消息循环运行
     throw new RuntimeException("Main thread loop unexpectedly exited");
    
    
    }
    ```

    Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出了。 从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。

    thread.attach(false)方法函数中便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。「Activity 启动过程」

    比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;

    再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。

    主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程

    system_server进程

    system_server进程是系统进程,java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。

    App进程

    App进程则是我们常说的应用程序,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),除了图中画的线程,其中还有很多线程

    Binder

    Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。

     

    结合图说说Activity生命周期,比如暂停Activity,流程如下:

    1. 线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
    2. 线程2通过binder传输到App进程的线程4;
    3. 线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
    4. 主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给 ActivityThread.H.handleMessage()方法,再经过方法的调用,
    5. 最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。

    补充:

    ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。

    从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。

    最后通过《Android开发艺术探索》的一段话总结 :

    ActivityThread通过ApplicationThread和AMS进行进程间通讯,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是。主线程的消息循环模型

    另外,ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类

    那么问题又来了,既然ActivityThread不是一个线程,那么ActivityThread中Looper绑定的是哪个Thread,也可以说它的动力是什么?

    3. ActivityThread 的动力是什么?

    进程 每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

    线程 线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

    其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。

    4. Handler 是如何能够线程切换
    其实看完上面我们大致也清楚线程间是共享资源的。所以Handler处理不同线程问题就只要注意异步情况即可。

    这里再引申出Handler的一些小知识点。 **** Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。(敲黑板)

    那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

    系统为什么不允许在子线程中访问UI?(摘自《Android开发艺术探索》) 这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个: ①首先加上锁机制会让UI访问的逻辑变得复杂 ②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。 所以最简单且高效的方法就是采用单线程模型来处理UI操作。

    那么问题又来了,子线程一定不能更新UI? 

    看到这里,又留下两个知识点等待下篇详解:View的绘制机制与Android Window内部机制。

    1. 子线程有哪些更新UI的方法。 主线程中定义Handler,子线程通过mHandler发送消息,主线程Handler的handleMessage更新UI。 用Activity对象的runOnUiThread方法。 创建Handler,传入getMainLooper。 View.post(Runnable r) 。

    runOnUiThread 第一种咱们就不分析了,我们来看看第二种比较常用的写法。

    先重新温习一下上面说的

    Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。(敲黑板)

        new Thread(new Runnable() {
            @Override
            public void run() {
    
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //DO UI method
    
                    }
                });
    
            }
        }).start();
    
    final Handler mHandler = new Handler();
    
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);//子线程(非UI线程)
        } else {
            action.run();
        }
    }
    

    进入Activity类里面,可以看到如果是在子线程中,通过mHandler发送的更新UI消息。 而这个Handler是在Activity中创建的,也就是说在主线程中创建,所以便和我们在主线程中使用Handler更新UI没有差别。 因为这个Looper,就是ActivityThread中创建的Looper(Looper.prepareMainLooper())。

    创建Handler,传入getMainLooper 那么同理,我们在子线程中,是否也可以创建一个Handler,并获取MainLooper,从而在子线程中更新UI呢? 首先我们看到,在Looper类中有静态对象sMainLooper,并且这个sMainLooper就是在ActivityThread中创建的MainLooper。

    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    

    所以不用多说,我们就可以通过这个sMainLooper来进行更新UI操作。

        new Thread(new Runnable() {
            @Override
            public void run() {
    
                Log.e("qdx", "step 1 "+Thread.currentThread().getName());
    
                Handler handler=new Handler(getMainLooper());
                handler.post(new Runnable() {
                    @Override
                    public void run() {
    
                        //Do Ui method
                        Log.e("qdx", "step 2 "+Thread.currentThread().getName());
                    }
                });
    
            }
        }).start();
    

    View.post(Runnable r) 老样子,我们点入源码

    //View

    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action); //一般情况走这里
        }
    
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }
    
        /**
         * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
         * handler can be used to pump events in the UI events queue.
         */
        final Handler mHandler;
    

    居然也是Handler从中作祟,根据Handler的注释,也可以清楚该Handler可以处理UI事件,也就是说它的Looper也是主线程的sMainLooper。这就是说我们常用的更新UI都是通过Handler实现的。

    另外更新UI 也可以通过AsyncTask来实现,难道这个AsyncTask的线程切换也是通过 Handler 吗? 没错,也是通过Handler……

    Handler实在是

    6.子线程中Toast,showDialog,的方法。 可能有些人看到这个问题,就会想: 子线程本来就不可以更新UI的啊 而且上面也说了更新UI的方法

    兄台且慢,且听我把话写完

        new Thread(new Runnable() {
            @Override
            public void run() {
    
                Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();//崩溃无疑
    
            }
        }).start();
    

    看到这个崩溃日志,是否有些疑惑,因为一般如果子线程不能更新UI控件是会报如下错误的(子线程不能更新UI)

    所以子线程不能更新Toast的原因就和Handler有关了,据我们了解,每一个Handler都要有对应的Looper对象,那么。 满足你。

        new Thread(new Runnable() {
            @Override
            public void run() {
    
                Looper.prepare();
                Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();
                Looper.loop();
    
            }
        }).start();
    

    这样便能在子线程中Toast,不是说子线程…? 老样子,我们追根到底看一下Toast内部执行方式。

    //Toast

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        ``````
    
        INotificationManager service = getService();//从SMgr中获取名为notification的服务
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
    
        try {
            service.enqueueToast(pkg, tn, mDuration);//enqueue? 难不成和Handler的队列有关?
        } catch (RemoteException e) {
            // Empty
        }
    }
    

    在show方法中,我们看到Toast的show方法和普通UI 控件不太一样,并且也是通过Binder进程间通讯方法执行Toast绘制。这其中的过程就不在多讨论了,有兴趣的可以在NotificationManagerService类中分析。

    现在把目光放在TN 这个类上(难道越重要的类命名就越简洁,如H类),通过TN 类,可以了解到它是Binder的本地类。在Toast的show方法中,将这个TN对象传给NotificationManagerService就是为了通讯!并且我们也在TN中发现了它的show方法。

    private static class TN extends ITransientNotification.Stub {//Binder服务端的具体实现类
    
        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show(IBinder windowToken) {
            mHandler.obtainMessage(0, windowToken).sendToTarget();
        }
    
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                IBinder token = (IBinder) msg.obj;
                handleShow(token);
            }
        };
    
    }
    

    看完上面代码,就知道子线程中Toast报错的原因,因为在TN中使用Handler,所以需要创建Looper对象。 那么既然用Handler来发送消息,就可以在handleMessage中找到更新Toast的方法。 在handleMessage看到由handleShow处理。

    //Toast的TN类

        public void handleShow(IBinder windowToken) {
    
                ``````
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    mWM.removeView(mView);
                }
                mWM.addView(mView, mParams);//使用WindowManager的addView方法
                trySendAccessibilityEvent();
            }
        }
    

    看到这里就可以总结一下:

    Toast本质是通过window显示和绘制的(操作的是window),而主线程不能更新UI 是因为ViewRootImpl的checkThread方法在Activity维护的View树的行为。 Toast中TN类使用Handler是为了用队列和时间控制排队显示Toast,所以为了防止在创建TN时抛出异常,需要在子线程中使用Looper.prepare();和Looper.loop();(但是不建议这么做,因为它会使线程无法执行结束,导致内存泄露)

    Dialog亦是如此。同时我们又多了一个知识点要去研究:Android 中Window是什么,它内部有什么机制?

    1. 如何处理Handler 使用不当导致的内存泄露? 首先上文在子线程中为了节目效果,使用如下方式创建Looper

              Looper.prepare();
              ``````
              Looper.loop();
      

      实际上这是非常危险的一种做法

    在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。(【 Looper.myLooper().quit(); 】)

    那么,如果在Handler的handleMessage方法中(或者是run方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露。

    具体可以参考Handler内存泄漏分析及解决

    总结一下,解决Handler内存泄露主要2点

    1 有延时消息,要在Activity销毁的时候移除Messages 2 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。

    总结

    想不到Handler居然可以腾出这么多浪花,与此同时感谢前辈的摸索。

    另外Handler还有许多不为人知的秘密,等待大家探索,下面我再简单的介绍两分钟

    • HandlerThread
    • IdleHandler

    HandlerThread

    HandlerThread继承Thread,它是一种可以使用Handler的Thread,它的实现也很简单,在run方法中也是通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在HandlerThread中创建Handler了。

    由于HandlerThread的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。

    HandlerThread的本质也是线程,所以切记关联的Handler中处理消息的handleMessage为子线程。

    IdleHandler

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }
    

    根据注释可以了解到,这个接口方法是在消息队列全部处理完成后或者是在阻塞的过程中等待更多的消息的时候调用的,返回值false表示只回调一次,true表示可以接收多次回调。

    具体使用如下代码

        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
    
                return false;
            }
        });
    

    另外提供一个小技巧:在HandlerThread中获取Looper的MessageQueue方法之反射。

    因为

    Looper.myQueue()如果在主线程调用就会使用主线程looper 使用handlerThread.getLooper().getQueue()最低版本需要23 //HandlerThread中获取MessageQueue

            Field field = Looper.class.getDeclaredField("mQueue");
            field.setAccessible(true);
            MessageQueue queue = (MessageQueue) field.get(handlerThread.getLooper());
    

    那么Android的消息循环机制是通过Handler,是否可以通过IdleHandler来判断Activity的加载和绘制情况(measure,layout,draw等)呢?并且IdleHandler是否也隐藏着不为人知的特殊功能?

    展开全文
  • 毕竟绝大多数程序属于用已经有的东西搭积木,所以我们更应该追求的速度。即使改得面目全非。可读性就交给说明文档和注释行吧~ 首先必须声明的,正常人干不出来这样的事儿。最近译文如何做一个编译器好像很红的...

    这篇文章通篇都是教你如何把你写的像垃圾一样的C源码变得像shi一样。

    当你看源码之前你必须要知道我写这篇文章只是拿很简单的东西在举例子:虽然降低了可读性,但是可以通过加注释行的方式来提高可读性。毕竟绝大多数程序属于用已经有的东西搭积木,所以我们更应该追求的是速度。即使改得面目全非。可读性就交给说明文档和注释行吧~

    首先必须声明的是,正常人干不出来这样的事儿。最近译文如何做一个编译器好像很红的样子,那么我们来讲一下如何把你写的已经很糟糕的C的源码变得更糟糕。使编译变得更简单,但是要建立在降低可读性的前提条件下。

    对,没错!能干出来这种事儿的除了我真是没谁了。编写一个程序的目的是把你其他写的本来就不好的源文件变得更糟。(雾) 实际上,说真的我追求的永远是代码的写的短小,说实话我喜欢短小的东西,比如我爱人的某一部分(污)

    在学习如何提高运行速度之前我们先看一下我们示例程序的提速成果:

    提速前:

    提速后:

    比如正常人求1到100的和的代码:

    #include<stdio.h> main()
    
    { int i=0,sum=0; for(i=1;i<=100;i++)
    
        {
    
            sum=sum+i;
    
        } printf("sum=%d\n",sum);
    
    }

    但是像我这种就会改一改:

    #include<stdio.h> main()
    
    { int i,sum=0; for(i=1;i<=100;i++)
    
            sum+=i; printf("sum=%d\n",sum);
    
    }

    似乎没变多少,然后我就会突然想起一个叫高斯的哥们儿。

    #include<stdio.h> main()
    
    { int sum=0;
    
        sum=(1+100)*100/2; printf("sum=%d\n",sum);
    
    }

    已经被我改到像垃圾一样了,但是,我这样的会把它弄的像shi一样的。

    #include<stdio.h> main()
    
    { int sum=5050; printf("sum=%d\n",sum);
    
    }

    然后,还不甘心:

    #include<stdio.h> main()
    
    { printf("sum=5050\n");
    
    }

    终于可以消停了。你可以看出来代码被我改的面目全非,但是其返回结果永远可是说是“正确”的。

     

    所有的代码是不是都可以这样呢?

    经过长期的实践,我得出了其中一部分是可以的,另一部分是不可以的。那么这种机械的任务,我们就可以让计算机帮我们干,特别是我现在在电子图书馆使用的这台像我写的源码一样shi的32位台式机。运行速度简直弱爆了。

    那么,我们就开始吧!首先在前几部分我要告诉你们一些非常简单的可以改的源码,接下来的部分会将如果想计算机帮我们做这件恶心事儿。

    首先就是只输出的程序,通常可以直接变成结果的输出,就比如求1到100的和这种程序。

    这种呢就是要分析最后的结果啦,直接变成hello world那种程序就好了。

    然后就是一类较为简单的输入输出的程序,虽然不能变得极为简单,但是也可以尽量的化简。

    一般正常的程序员是这样写的:

    #include<stdio.h> main()
    
    { int i=0,j=0,sum=0; scanf("%d%d",&i,&j); while(i<=j)
    
        {
    
            sum=sum+i;
    
            i++;
    
        } printf("sum=%d\n",sum);
    
    }

    聪明一些的程序员是这样写的:

    #include<stdio.h> main()
    
    { int i,j,sum=0; scanf("%d%d",&i,&j); while(i<=j)
    
        {
    
            sum+=i;
    
            i++;
    
        } printf("sum=%d\n",sum);
    
    }

    如果你也认识高斯那哥们儿,你可能会这样写

    #include<stdio.h> main()
    
    { int i,j,sum=0; scanf("%d%d",&i,&j);
    
        sum=(i+j)*(j-i+1)/2; printf("sum=%d\n",sum);
    
    }

    你要知道,更少的括号是给编译的时候省力的秘诀,当然我也给了一种看门儿大爷没告诉你的求和公式,当然这种公式的使用前提是公差是1。

    这时候你会告诉我,你的源码变成了:

    #include<stdio.h> main()
    
    { int i,j,sum=0; scanf("%d%d",&i,&j);
    
        sum=(j*j-i*i+j+i)/2; printf("sum=%d\n",sum);
    
    }

    当然,我会把它写得更短小:

    #include<stdio.h> main()
    
    { int i,j; scanf("%d%d",&i,&j); printf("sum=%d\n",(j*j-i*i+j+i)/2);
    
    }

    为什么不把sum的值写成更简单的多项式的形式呢?换句话说,你使用的括号还可以更少:

    #include<stdio.h> main()
    
    { int i,j; scanf("%d%d",&i,&j); printf("sum=%d\n",j*j/2-i*i/2+j/2+i/2);
    
    }

    你会发现这里面实际上还有可以简化的部分,可以吧除以2变成向右移一位

    但是,我们知道位运算的优先级要低于加减法,所以,源码会变成这样:

    #include<stdio.h> main()
    
    { int i,j; scanf("%d%d",&i,&j); printf("sum=%d\n",j*j-i*i+j+i>>1);
    
    }

     

    附录:

    演示程序提速前源码:

    #include<stdio.h> main()
    
    { int i=0,j=100,sum=0; while(i<=j)
    
      {
    
        sum=sum+i;
    
        i++;
    
      } printf("sum=%d\n",sum);
    
    }

    演示程序提速后源码:

    #include<stdio.h> main()
    
    { int i=0,j=100; printf("sum=%d\n",j*j-i*i+j+i>>1);
    
    }

    你一定会问,这样程序的可读性降低了很多,我觉得这些你可以通过注释行来弥补

    当然,我写文章的目的是做一个简化的程序出来,那么你只需要掌握简化的基本思路,就可以了。永远记住,循环和括号是我们讨厌的东西,除了不得已的时候,我们不会使用的。

    最后,不管你是转行也好,初学也罢,进阶也可,如果你想学编程~

    【值得关注】我的 C/C++编程学习交流俱乐部【点击进入】

    问题答疑,学习交流,技术探讨,还有超多编程资源大全,零基础的视频也超棒~

    展开全文
  • 对于一个初学者来说for循环可能会击溃很多人,因为网上找的python的for循环没有详细的介绍for循环的条件以及for循环后面定义的东西是什么意思。首先我先举一个常用的例子。for i in range(1,10) :print i上面这两条...

    对于一个初学者来说for循环可能会击溃很多人,因为网上找的python的for循环没有详细的介绍for循环的条件以及for循环后面定义的东西是什么意思。首先我先举一个常用的例子。

    for i in range(1,10) :

    print i

    上面这两条代码是用来在屏幕上打印1到9的。这里就不对range这个函数进行介绍了,大家可以去百度查一下。或者我之后的文章会介绍。

    这里i是自己定义的一个变量in后面的range(1,10)是条件。这个循环从开始执行到执行结束的的走法是首先range内的东西给i赋值上然后走进循环打印出i被赋值的东西打印完成之后回到for查看是否循环完毕没有循环完毕则将range生成的第二个数字往i上面赋值然后继续打印这样一直循环到range不在生成数字了。在来另外一条网上用的多的

    for i in 'hello,world':

    print i

    这行代码会把hello,world一个一个打印到屏幕上面。还是那样for后面先定义一个i变量在把hello,world这串字符串里面的字符一个一个的赋值到i上运行的时候在通过print打印到屏幕上。关于python的for循环就到这里这里只是一个小结。以后还会讲到更多的关于for循环的知识。

    也欢迎大家在评论区指出不足的地方。我会慢慢改进的。

    展开全文
  • 瞎扯 昨天写了bus的生产者与消费者. 今天就写写looper ...其实大部分人应该都知道looper循环,不断的取消息,然后调用handler处理. 启动过程: 翻一下Handler的源码 构造函数.里的一段 mLooper = Loo...

    瞎扯

    昨天写了bus的生产者与消费者. 今天就写写looper 为什么不和handler一起写呢?因为懒.

    Looper

    loop翻译一下,回路,循环

    大致就是循环的意思.

    android里也一样.一个可以无限的死循环

    其实大部分人应该都知道looper是个循环,不断的取消息,然后调用handler处理.

    启动过程:

    翻一下Handler的源码 构造函数.里的一段

       mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
    复制代码

    handler必须持有一个Looper对象

    Looper对象怎么来?

        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    复制代码

    Looper.prepare() 应该很熟悉吧.

    再来一段代码,子线程中创建Handler:

    public void test() {
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    Looper.prepare();
                    Handler handler = new Handler();
                    Looper.loop();
                }
            }.start();
        }
    复制代码

    经历不深的人,看着这一段,肯定是懵的. 为什么调下Looper.prepare()就行了.怎么跟handler联系上的. 调Looper.loop();的干嘛?

    Looper怎么跟handerl关联上的.明天再写.哈哈 今天就写为什么要调Looper.loop();

    写份伪代码你应该就明白了.

    public void test2() {
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    Looper looper = new Looper();//创建Looper,
                    Handler handler = new Handler(looper);//handler绑定looper
                    looper.loop();//looper开启循环
                }
            }.start();
        }
    
        public class Looper {
    
            public Looper prepare() {
                return new Looper();
            }
    
            public void loop() {
                for (; ; ) {
    //              msg.target.dispatchMessage(msg);
                    handler.dispatchMessage(msg);//死循环,调用handler的回调处理结果.
                }
            }
        }
    复制代码

    去掉那些复杂的代码. 其本质差不多就这样,略过了MessageQueue. MessageQueue就是个有序队列,其实没啥好讲的.一个集合而已.

    looper.loop();为什么要在线程的最后调用?

    因为死循环啊...不在最后调用在哪调用. 还有为了让这个线程不结束啊. 不信可以试试在loop()后面再写代码.你看会不会往下走- -

    把方法里的内容展开一下

      new Thread() {
                @Override
                public void run() {
                    super.run();
                    Looper looper = new Looper();
                    Handler handler = new Handler(looper);
                    for (; ; ) {
    //              msg.target.dispatchMessage(msg);
                        handler.dispatchMessage(msg);
                    }
                    //如果不手动调用,looper怎么工作呢, 并且保证执行在线程的最后面- -
                    //可以试试,不调用loop(), handler会不会的回调会不会走.
                }
            }.start();
    复制代码

    看看ams中如何创建的主线程的handler,looper

    这个ServiceThread是什么鬼

    是不是我前面说的那么回事.哈哈

    总结:

    AMS,启动了主线程. 然后我们写的所有activity,fragment之类的代码,什么onCreate,onStart里的代码. 其实都相当于,执行在Looper.loop()里面.通过handler发送消息.处理消息在不停的调用.方法调方法这样不停的.如果你不停的debug下一行,就会发现跑到Looper里面去了.就是这个道理. 所以,当我们某个UI更新操作逻辑过于复杂,就会出现卡死的情况. onstart,onparse等生命周期里面逻辑太复杂也会造成ANR.因为主线程走不下去了.肯定没法响应.

    主线程的Looper退出循环,意味着,主线程结束,程序退出.

    交流群:493180098,这是个很少吹水,交流学习的群.

    转载于:https://juejin.im/post/5cdb70def265da037516c4de

    展开全文
  • hash,一般翻译做散列、杂凑,或音译为哈希,把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换一种压缩映射,也就是,散列值的空间通常远小于输入的空间...
  • RUN Loop是什么? 1。runloop是事件接收和分发机制的一个实现。 2。什么时候使用runloop 当需要和该线程进行交互的时候。主线程默认有runloop。当自己启动一个线程,如果只是用于处理单一的事件,则该线程...
  • RUN Loop是什么? 1。runloop是事件接收和分发机制的一个实现。 2。什么时候使用runloop 当需要和该线程进行交互的时候。主线程默认有runloop。当自己启动一个线程,如果只是用于处理单一的事件,则该线程...
  • 1. 什么是 RNN 循环神经网络(Recurrent Neural Network,RNN)一种以序列数据为输入来进行建模的深度学习模型,它 NLP 中最常用的模型。 2. RNN 的结构 RNN 有多种结构如下图所示: 我们从基础的神经网络中...
  • 前言迭代器、生成器、装饰器都有一个“器”,但他们之间没有什么关系。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter()和next()。一、...
  • 循环引用问题,普通控件为什么用weak,代理为什么用weak,block内用到外面的东西也要用weak,当控制器想要销毁时,控制器被一个强指针指着,而scrollView添加到控制器view中也被强指针指着,也无法销毁,那么它的...
  • 它在反复循环的事情上,比如改变数组,就需要重新循环出来。 如果没有设置key值的话,它就会偷懒,也就是已经有的东西,它就不会去改了,不会去重新删掉对象,修正过来,而是直接去改它的值。 如果设置了key值,它...
  • 今天的文章我们主要来讲讲图中的这个东西究竟是什么?你们知道他是干什么的吗,这个东西究竟有什么作用?这是前几天的一个微信网友添加我微信问我的问题,今天对这个问题整理了一下,顺来来科普一下关于这个系统的...
  • 迭代器、生成器、装饰器都有一个“器”,但他们之间没有什么关系。 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。 迭代器有两个基本的方法:iter()和next()。 一、...
  • 本文要学习的内容:1.指针在C / C ++中的重要性。...普通指针的问题:通过使用此示例,使用一个小型C ++程序,让我们了解普通指针的主要问题是什么。因此,发生的事情是它将有一个指针“ p”,它将指向一个矩...
  • 代码一个非常灵活的的东西,很多代码样子不一致,但是表达的意义确基本一致的,这就跟我们接下来要了解的多种循环遍历方法一样,一起来看下吧~Python中遍历列表有以下几种方法:一、for循环遍历lists=["m1",1900...
  • 初学者来说for循环可能会击溃很多人,因为网上找的python基础课程的for循环没有详细的介绍for循环的条件以及for循环后面定义的东西是什么意思。首先我先举一个常用的例子。for i in range(1,10) :print i上面这两条...
  • RUN Loop是什么? 1。runloop是事件接收和分发机制的一个实现。 2。什么时候使用runloop 当需要和该线程进行交互的时候。主线程默认有runloop。当自己启动一个线程,如果只是用于处理单一的事件,则该线程在执行...
  • 回顾下,源码分析: 这里最重要的就在获取三级缓存时候的 getObject(). ...我们看下getEarlyBeanReference什么东西?他一个函数式接口 @FunctionalInterfacepublic interface ObjectFactory
  • Loop里的死循环什么没有阻塞线程?      我们知道,更新ui等一些耗时操作都不能放在主线程去执行,最好在子线程中执行,那我们执行好的任务又怎么通知主线程去更新数据呢?答案就是Handler...
  • 循环队列

    2021-01-31 23:02:00
    问题描述: 循环队列一般队列的变种吧,就是将队列首尾相连了,貌似这样就不必考虑队列满而无法使用了,...像一条蛇咬住自己的尾巴,而且蛇腔里面可以填充东西的感觉。 为什么循环队列: 主要为了克服“假溢
  • 这些变化对现在从事软件开发的数百万人意味着什么?我们会看到工作流失和裁员,还是会看到编程演变成不同的东西,甚至可能会更加专注于使用户满意? 自1970年代以来,我们几乎以相同的方式构建软件。我们拥有高级...
  • 构造循环外部的代码以实现这一点通常一个好主意,无论它的分支来允许更好的循环结构,还是部分剥离第一次迭代,这样您就可以在一个方便的点进入循环,并可能保存一个mov指令或它里面的任何东西(不确定是否有名称...
  • 循环重定向

    2021-01-08 14:52:43
    <div><p>试了好几个版本的filter_module,全是循环重定向,上星期还是正常的,应该谷歌改了什么东西</p><p>该提问来源于开源项目:cuber/ngx_http_google_filter_module</p></div>
  • // 当循环结束时,所有用户信息都封装到ArrayList集合中 DBUtil.closeAll(conn, ps, rs); return allcomment; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace...
  • 断言是什么?(编程里面的东西

    万次阅读 2007-06-15 22:30:00
    例如有一个单链表,无头,单向不循环,结点含data,next,现在写一个函数,获取指针所指结点的下一个结点的data:int GetNext(pNode* pn){//如果为空,则出错,因为没有后续结点,没有这一条的话如果传入NULL则程序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,467
精华内容 586
关键字:

循环是什么东西