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.pswp.cn/news/390414.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390414.shtml
英文地址,請注明出處:http://en.pswp.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的經典《機…