Android基础夯实--你了解Handler有多少?

概述

对于刚入门的同学来说,往往都会对Handler比较迷茫,到底Handler是个什么样的东西。当然,可能对于一些有工作经验的工程师来说,他们也不一定能很准确地描述,我们来看下API的介绍。

Handler是用来结合线程的消息队列来发送、处理“Message对象”和“Runnable对象”的工具。每一个Handler实例之后会关联一个线程和该线程的消息队列。当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。

从官方文档中,我们不难找出其中的关键词,就是“线程”。我们都知道,一个涉及到网络操作,耗时操作等的Android应用,都离不开多线程操作,然而,如果这时我们允许并发更新UI,那么最终导致控件的状态都是不可确定的。所以,我们可以通过对控件进行加锁,在不需要用时解锁,这是一个解决方案之一,但最后很容易造成线程阻塞,效率会非常差。所以,谷歌采用了只允许在主线程更新UI,所以作为线程通信桥梁的Handler也就应运而生了。

Looper、MessageQueue、Message、Handler的关系

讲到Handler,肯定离不开Looper、MessageQueue、Message这三者和Handler之间的关系,下面简略地带过,详细自己可以查阅相关资料,或者查看源码,这样更方便大家深入学习。

Looper

每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

那么就会有人问,为什么我们使用Handler的时候从来都不需要创建Looper呢?这是因为在主线程中,ActivityThread默认会把Looper初始化好,prepare以后,当前线程就会变成一个Looper线程。

MessageQueue

MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。

Message

消息对象,就是MessageQueue里面存放的对象,一个MessageQueu可以包括多个Message。当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

四者关系总体如下(如有不对的地方,谢谢指出)
image

Handler的主要用途

  1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
  2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去。

废话不多说,通过举例来说明Handler的两个主要用途。

1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列

实例:通过Handler配合Message或者Runnable实现倒计时

  • 首先看一下效果图

image

  • 方法一,通过Handler + Message的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding;private Handler mHandler ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);//设置监听事件mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//通过Handler + Message的方式实现倒计时for (int i = 1; i <= 10; i++) {Message message = Message.obtain(mHandler);message.what = 10 - i;mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息}}});mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息}};}
}

其实代码不用怎么解释,都比较通俗易懂,但是这里用到了DataBiding,可能没用过的同学看起来有点奇怪,但其实反而简略了代码,有一定基础的同学看起来都不会有太大压力,所以不做太多解释。通过这个小程序,作者希望大家可以了解到Handler的一个作用就是,在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。

  • 方法二,通过Handler + Runnable的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {private ActivityMainBinding mBinding;private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);//设置监听事件mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {for (int i = 1; i <= 10; i++) {final int fadedSecond = i;//每延迟一秒,发送一个Runnable对象mHandler.postDelayed(new Runnable() {@Overridepublic void run() {mBinding.time.setText((10 - fadedSecond) + "");}}, 1000 * i);}}});}
}

方法二也是通过代码让大家加深Handler处理有序事件的用途,之所以分开Runnable和Message两种方法来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。只要大家看一下源码就可以知道,Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了,至于为什么会分开两种方法,下文会给大家详说究竟。下面再来看看Handler的第二个主要用途。

2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去

实例:通过Handler + Message来实现子线程加载图片,在UI线程显示图片

  • 效果图如下

image

  • 代码如下(布局代码也不放出来了)

public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {private ActivityThreadBinding mBinding = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);// 设置点击事件mBinding.clickBtn.setOnClickListener(this);mBinding.resetBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {// 响应load按钮case R.id.clickBtn:// 开启一个线程new Thread(new Runnable() {@Overridepublic void run() {// 在Runnable中进行网络读取操作,返回bitmapfinal Bitmap bitmap = loadPicFromInternet();// 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可Handler handler = new Handler(Looper.getMainLooper());// 通过Handler的post Runnable到UI线程的MessageQueue中去即可handler.post(new Runnable() {@Overridepublic void run() {// 在MessageQueue出队该Runnable时进行的操作mBinding.photo.setImageBitmap(bitmap);}});}}).start();break;case R.id.resetBtn:mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));break;}}/**** HttpUrlConnection加载图片,不多说* @return*/public Bitmap loadPicFromInternet() {Bitmap bitmap = null;int respondCode = 0;InputStream is = null;try {URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(10 * 1000);connection.setReadTimeout(5 * 1000);connection.connect();respondCode = connection.getResponseCode();if (respondCode == 200) {is = connection.getInputStream();bitmap = BitmapFactory.decodeStream(is);}} catch (MalformedURLException e) {e.printStackTrace();Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();} catch (IOException e) {e.printStackTrace();} finally {if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}}return bitmap;}
}

Handler推送Message和Runnable的区别

在上文我们通过用Handler推送Message和Runnable实现相同的倒计时效果,这里我们就说一下Post(Runnable)和SendMessage(Message)的区别。

首先我们看看post方法和sendMessage方法的源码:

    public final boolean post(Runnable r){return  sendMessageDelayed(getPostMessage(r), 0);}
    public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}

可见,两个方法都是通过调用sendMessageDelayed方法实现的,所以可以知道它们的底层逻辑是一致的。

但是,post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage(r)来将Runnable对象来转为Message,我们点进方getPostMessage()法可以看到:

    private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}

其实,最终runnable最终也是转化为一个Message,而这个Message只有一个被赋值的成员变量,就是Runnable的回调函数,也就是说,这个Message在进入MessageQueue之后,它只是一个“动作”,即我们Runnbale的run方法里面的操作。

要知道,我们的Message类可是有很多参数的,所以你可以理解为它是一个非常丰富的JavaBean,可以看看它的成员变量:

  • public int what;
  • public int arg1;
  • public int arg2;
  • public Object obj;
  • ...

那么讲到这里,大家也应该有所理解为什么Google工程师为什么会封装这两种方法,我总结如为:为了更方便开发者根据不同需要进行调用。当我们需要传输很多数据时,我们可以使用sendMessage来实现,因为通过给Message的不同成员变量赋值可以封装成数据非常丰富的对象,从而进行传输;当我们只需要进行一个动作时,直接使用Runnable,在run方法中实现动作内容即可。当然我们也可以通过Message.obtain(Handler h, Runnable callback)来传入callback接口,但这样看起来就没有post(Ruannable callback)那么直观。

API

API是我们学习最好的文档,所以我也简要跟大家学习一下,其实大家认真看我上面的介绍加上自己亲手实践,Handler的API大家都可以随便翻阅了。

构造函数

  • Handler()
  • Handler(Handler.Callback callback):传入一个实现的Handler.Callback接口,接口只需要实现handleMessage方法。
  • Handler(Looper looper):将Handler关联到任意一个线程的Looper,在实现子线程之间通信可以用到。
  • Handler(Looper looper, Handler.Callback callback)

主要方法

  • void dispatchMessage (Message msg)

一般情况下不会使用,因为它的底层实现其实是作为处理系统消息的一个方法,如果真要用,效果和sendMessage(Message m)效果一样。

    public void dispatchMessage(Message msg) {if (msg.callback != null) {// 如果有Runnbale,则直接执行它的run方法handleCallback(msg);} else {//如果有实现自己的callback接口if (mCallback != null) {//执行callback的handleMessage方法if (mCallback.handleMessage(msg)) {return;}}//否则执行自身的handleMessage方法handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}
  • void dump (Printer pw, String prefix)

主要Debug时使用的一个方法,dump函数只是使用了Printer对象进行了打印,打印出Handler以及Looper和Queue中的一些信息,源码如下:

    public final void dump(Printer pw, String prefix) {pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());// 如果Looper为空,输出Looper没有初始化if (mLooper == null) {pw.println(prefix + "looper uninitialized");} else {// 否则调用Looper的dump方法,Looper的dump方法也是mLooper.dump(pw, prefix + "  ");}}

通过测试用例大家会了解得更清晰:

        //测试代码Printer pw = new LogPrinter(Log.ERROR, "MyTag");mHandler.dump(pw, "prefix");

结果:
image

  • Looper getLooper ()

拿到Handler相关联的Looper

  • String getMessageName (Message message)

获取Message的名字,默认名字为message.what的值。

  • void handleMessage (Message msg)

处理消息。

  • boolean hasMessages (int what)

判断是否有Message的what值为参数what。

  • boolean hasMessages (int what, Object object)

判断是否有Message的what值为参数what,obj值为参数object。

  • Message obtainMessage (int what, Object obj)

从消息池中拿到一个消息并赋值what和obj,其他重载函数同理。

  • boolean post (Runnable r)

将Runnable对象加入MessageQueue。

  • boolean post (Runnable r)

将Runnbale加入到消息队列的队首。但是官方不推荐这么做,因为很容易打乱队列顺序。

  • boolean postAtTime (Runnable r, Object token, long uptimeMillis)

在某个时间点执行Runnable r。

  • boolean postDelayed (Runnable r, long delayMillis)

当前时间延迟delayMillis个毫秒后执行Runnable r。

  • void removeCallbacks (Runnable r, Object token)

移除MessageQueue中的所有Runnable对象。

  • void removeCallbacksAndMessages (Object token)

移除MessageQueue中的所有Runnable和Message对象。

  • void removeMessages (int what)

移除所有what值得Message对象。

  • boolean sendEmptyMessage (int what)

直接拿到一个空的消息,并赋值what,然后发送到MessageQueue。

  • boolean sendMessageDelayed (Message msg, long delayMillis)

在延迟delayMillis毫秒之后发送一个Message到MessageQueue。

Handler引发的内存泄漏

在上面的例子中,为了展示方便,我都没有考虑内存泄漏的情况,但是在实际开发中,如果不考虑代码的安全性的话,尤其当一个项目到达了一定的规模之后,那么对于代码的维护和系统的调试都是非常困难的。而Handler的内存泄漏在Android中也是一个非常经典的案例。

详细可以参考:How to Leak a Context: Handlers & Inner Classes

或参考翻译文:Android中Handler引起的内存泄露

通常我们都会在一个Activity内部定义一个Handler的内部类:

public class MainActivity extends AppCompatActivity {private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what){...}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {...}}, 1000000);}
}

(1)外部类Activity中定义了一个非静态内部类Handler,非静态内部类默认持有对外部类的引用。如果外部Activity突然关闭了,但是MessageQueue中的消息还没处理完,那么Handler就会一直持有对外部Activty的引用,垃圾回收器无法回收Activity,从而导致内存泄漏。

(2) 如上代码,在postDelayed中,我们在参数中传入一个非静态内部类Runnable,这同样会造成内存泄漏,假如此时关闭了Activity,那么垃圾回收器在接下来的1000000ms内都无法回收Activity,造成内存泄漏。

解决方案:

(1) 将非静态内部类Handler和Runnable转为静态内部类,因为非静态内部类(匿名内部类)都会默认持有对外部类的强引用。

(2) 改成静态内部类后,对外部类的引用设为弱引用,因为在垃圾回收时,会自动将弱引用的对象回收。

避免内存泄漏的例子:

public class HandlerActivity extends AppCompatActivity {private final MyHandler mHandler = new MyHandler(this);private static final Runnable mRunnable = new Runnable() {@Overridepublic void run() {// 操作}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_fourth);mHandler.postDelayed(mRunnable, 1000*10);finish();   }private static class MyHandler extends Handler {WeakReference<HandlerActivity> mWeakActivity;public MyHandler(HandlerActivity activity) {this.mWeakActivity = new WeakReference<HandlerActivity>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);final HandlerActivity mActivity = mWeakActivity.get();if (mActivity != null) {// 处理消息}}}}

HandlerThread

思考一下,假如我们需要同时下载A和B,下载A需要6s,下载B需要5s,在它们下载完成后Toast信息出来即可,此时HandlerThread便是一种解决方式之一。那么HandlerThread到底是什么?

  • HandlerThread就是一种线程。
  • HandlerThread和普通的Thread之间的区别就是HandlerThread在创建的时候会提供自己该线程的Looper对象。

所以,如果大家了解清楚了我前面所讲的Looper、Message、Handler、MessageQueue的关系的话,这里就很清楚HandlerThread是什么东西了。大家都知道,我们在Actvity创建时系统会自动帮我们初始化好主线程的Looper,然后这个Looper就会管理主线程的消息队列。但是在我们创建子线程时,系统并不会帮我们创建子线程的Looper,需要我们自己手动创建,如下:

    new Thread(){@Overridepublic void run() {super.run();Looper.prepare();Handler mHandler = new Handler(Looper.myLooper());Looper.loop();}}.start();

所以HandlerThread就在内部帮我们封装了Looper的创建过程,从源码可以看到,HandlerThread集成于Thread,然后覆写run方法,进行Looper的创建,从而通过getLooper方法暴露出该线程的Looper对象

public class HandlerThread extends Thread {int mPriority;int mTid = -1;Looper mLooper;...@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {}}}return mLooper;}...
}    

所以通过HandlerThread,我们可以轻松创建一个包含了Looper的子线程:

final HandlerThread mHandlerThread = new HandlerThread("HandlerThread");mHandlerThread.start();Handler mHandler = new Handler(mHandlerThread.getLooper());

使用HandlerThread同时下载A和B的Demo:

image

代码:

public class HandlerThreadActivity extends AppCompatActivity {private TextView tv_A, tv_B;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handler_thread);tv_A = (TextView) findViewById(R.id.txt_dlA);tv_B = (TextView) findViewById(R.id.txt_dlB);final Handler mainHandler = new Handler();final HandlerThread downloadAThread = new HandlerThread("downloadAThread");downloadAThread.start();Handler downloadAHandler = new Handler(downloadAThread.getLooper());// 通过postDelayed模拟耗时操作downloadAHandler.postDelayed(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "下载A完成", Toast.LENGTH_SHORT).show();mainHandler.post(new Runnable() {@Overridepublic void run() {tv_A.setText("A任务已经下载完成");}});}}, 1000 * 5);final HandlerThread downloadBThread = new HandlerThread("downloadBThread");downloadBThread.start();Handler downloadBHandler = new Handler(downloadBThread.getLooper());// 通过postDelayed模拟耗时操作downloadBHandler.postDelayed(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "下载B完成", Toast.LENGTH_SHORT).show();mainHandler.post(new Runnable() {@Overridepublic void run() {tv_B.setText("B任务已经下载完成");}});}}, 1000 * 7);}
}

总结

由于Android的UI更新只能在主线程,所以Handler是Android中一套非常重要的更新UI线程机制,虽然在很多框架的帮助下我们可以减少了很多Handler的代码编写,但实际上很多框架的底层实现都是通过Handler来更新UI的,所以可见掌握好Handler对我们来说是多么重要,所以这也是很多面试官在面试中的高频考点之一。虽然Handler对开发者来说是一个非常方便的存在,但是我们也不能否认它也是存在缺点的,如处理不当,Handler所造成的的内存泄漏对开发者来说也是一个非常头疼的难题。

转载于:https://www.cnblogs.com/ryanleee/p/8204450.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/390414.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring与springBoot不同之处

( 1&#xff09;遵循“习惯优于配置”的原则&#xff0c;使用Spring Boot只需要很少的配置&#xff0c;大部分的时候我们直接使用默认的配置即可&#xff1b; &#xff08;2&#xff09;项目快速搭建&#xff0c;可以无需配置的自动整合第三方的框架&#xff1b; &#xff08;3…

sketch-a-net_Adobe XD,Sketch,Figma,InVision-如何在2020年选择最佳设计软件

sketch-a-netComparing Adobe XD vs Sketch vs Figma vs InVision studio is a very common topic among designers who are looking for the best design software. 在寻求最佳设计软件的设计师中&#xff0c;比较Adobe XD&#xff0c;Sketch&#xff0c;Figma和InVision Stud…

merge intervals(合并间隔)

Given a collection of intervals, merge all overlapping intervals. For example,Given [1,3],[2,6],[8,10],[15,18],return [1,6],[8,10],[15,18]. 题目没有说所有间隔的start是依次增加的。所以&#xff0c;为了方便讨论&#xff0c;我们要将所有间隔按照start升序排列。因…

剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数&#xff08;Ugly Number&#xff09;。求按从小到大的顺序的第 n 个丑数。 示例: 输入: n 10 输出: 12 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 说明: 1 是丑数。n 不超过1690。 解题思路 使用小根堆&#xf…

维护旧项目_为什么您的旧版软件难以维护-以及如何处理。

维护旧项目Believe it or not, some organizations still rely on legacy software to carry out operations even though newer and more versatile options are available. We know that “old is gold”, but legacy applications cannot glitter forever. As such, these o…

python--内置函数

内置函数现在python一共为我们提供了68个内置函数&#xff0c;讲述过程&#xff1a;一、其他中的12个 &#xff08;一&#xff09;执行 字符串 类型代码的执行 1 eval执行有意义的字符串 ,有返回值 print(eval(12))print(eval("print(美丽)")) #美丽 2 ex…

Nancy简单实战之NancyMusicStore(四):实现购物车

原文:Nancy简单实战之NancyMusicStore(四)&#xff1a;实现购物车前言 上一篇&#xff0c;我们完成了商品的详情和商品的管理&#xff0c;这一篇我们来完成最后的一个购物车功能。 购物车&#xff0c;不外乎这几个功能&#xff1a;添加商品到购物车&#xff0c;删除购物车中的商…

剑指 Offer 32 - I. 从上到下打印二叉树

从上到下打印出二叉树的每个节点&#xff0c;同一层的节点按照从左到右的顺序打印。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3/ \9 20/ \15 7返回&#xff1a; [3,9,20,15,7] 提示&#xff1a; 节点总数 < 1000 解题思路 使用队列实现层序遍历 代码 /*** …

数据库表命名 单数复数_数据是还是数据是? “数据”一词是单数还是复数?

数据库表命名 单数复数Ill cut right to the chase: the word "data" is plural. Its the plural form of Latin word "datum." Many data. One datum.我将紧追其后&#xff1a;“数据”一词是复数形式。 它是拉丁文“基准”的复数形式。 许多数据。 一个基…

《七步掌握业务分析》读书笔记六

分析技术和呈现格式 词汇表 强有力沟通的一个重要内容是一致地使用术语和惯用语。每次谈话都涉及对术语的共同理解。 工作流图&#xff08;也称为流程图、UNL活动图和过程图&#xff09; 工作流程把一个或多个业务过程的细节可视化地呈现出来&#xff0c;以澄清理解或提出过程改…

Mysql数据库--语句整理/提升/进阶/高级使用技巧

一、基础 1、说明&#xff1a;创建数据库CREATE DATABASE database-name 2、说明&#xff1a;删除数据库drop database dbname3、说明&#xff1a;备份sql server--- 创建 备份数据的 deviceUSE masterEXEC sp_addumpdevice disk, testBack, c:\mssql7backup\MyNwind_1.dat--- …

1104. 二叉树寻路

在一棵无限的二叉树上&#xff0c;每个节点都有两个子节点&#xff0c;树中的节点 逐行 依次按 “之” 字形进行标记。 如下图所示&#xff0c;在奇数行&#xff08;即&#xff0c;第一行、第三行、第五行……&#xff09;中&#xff0c;按从左到右的顺序进行标记&#xff1b;…

javascript 代码_如何开始对JavaScript代码进行单元测试

javascript 代码We all know we should write unit tests. But, its hard to know where to start and how much time to devote to tests compared to actual implementation. So, where to start? And is it just about testing code or do unit tests have other benefits?…

个人作业——软件工程实践总结作业

一、请回望暑假时的第一次作业&#xff0c;你对于软件工程课程的想象 1&#xff09;对比开篇博客你对课程目标和期待&#xff0c;“希望通过实践锻炼&#xff0c;增强计算机专业的能力和就业竞争力”&#xff0c;对比目前的所学所练所得&#xff0c;在哪些方面达到了你的期待和…

(转)在阿里,我们如何管理代码分支?

阿里妹导读&#xff1a;代码分支模式的选择并没有绝对的正确和错误之分&#xff0c;关键是与项目的规模和发布节奏相匹配。阿里协同研发平台在经过众多实践历练后&#xff0c;总结出了一套独创的分支管理方法&#xff1a;AoneFlow&#xff0c;通过兼备灵活高效与简单实用的流程…

WIN10系统 截图或者某些程序时屏幕会自动放大怎么办

右击这个应用程序&#xff0c;兼容性&#xff0c;以兼容模式运行&#xff0c;同时勾选高DPI设置时禁止显示缩放即可

css背景图片添加url_CSS背景图片–如何向您的Div添加图片URL

css背景图片添加urlSay you want to put an image or two on a webpage. One way is to use the background-image CSS property. 假设您要在网页上放置一两个图片。 一种方法是使用background-image CSS属性。 This property applies one or more background images to an el…

golang基础01

1.环境变量&#xff1a;go env//代码目录和第三方库文件set GOPATHC:\Users\hanxiaodong\go//go安装目录set GOROOTC:\Gopath里要配置&#xff1a;goroot/bin;和gopath/bin; gopath目录下三个文件夹&#xff1a;pkg&#xff1a;编译好的库文件 .a 文件bin&#xff1a;可执行文件…

hugo 能做web开发吗_如何自托管Hugo Web应用

hugo 能做web开发吗After hosting with Netlify for a few years, I decided to head back to self hosting. There are a few reasons for that, but the main reasoning was that I had more control over how things worked. 在Netlify托管了几年之后&#xff0c;我决定回到…

资源 | 深度学习课程入门与介绍

【1】Andrew NG Deep Learning.ai http://deeplearning.ai/网易云课堂&#xff08;中文字幕&#xff09;&#xff1a;http://mooc.study.163.com/smartSpec/detail/1001319001.htm推荐理由&#xff1a;Andrew Ng老师是讲课的能手&#xff0c;很多人认识他是从Stanford的经典《机…