Android7.0多視窗實現原理(一)
概述
在Android N(7.0)版本開始,系統支援了多視窗功能。在有了多視窗支援之後,使用者可以同時開啟和看到多個應用的介面。並且系統還支援在多個應用之間進行拖拽。在大螢幕裝置上,這一功能非常實用。
在Android N中多視窗有三種表現形式:
- 分屏模式
該模式主要將螢幕一分為二,同時出現兩個應用介面。
2.畫中畫模式
該模式主要在視訊播放中使用,可以使視訊播放視窗一直處於最頂層。
3.FreeForm模式
該模式類似於我們的桌面作業系統,應用介面視窗可以自由拖動和修改大小。
而本文主要講解多視窗分屏模式的實現方式。
分屏總覽
分屏模式是通過長按最近任務列表(RecetsActivity)的任一個歷史應用(TaskTiew)進入的,如果該應用不支援分屏就提示使用者,如果可以分屏就顯示可以分屏的區域。之後拖拽想要分屏的TaskView,在拖拽的過程中不斷的不斷進行判斷touch事件移動的位置是否進入了分屏區域,如果沒有繼續處理touch事件,如果進入了分屏區域,就會更新螢幕區域分屏,此時結束拖拽,呼叫AMS為分屏區域建立stack,根據螢幕尺寸計算stack的尺寸,然後對stack中task再重新計算尺寸,最後啟動分屏應用。
請求分屏
最近任務列表是由一個個的TaskView組成,當我們選擇要分屏的TaskVIew,長按就進入了TaskVIew.java的onLongClick函式中,請求對其分屏。
TaskView發生長按事件後,獲取TaskView的邊界值,進行判斷mDownTouchPos的x,y值是否在TaskView的範圍之內,該值就是觸控事件上報的值,也就是手指按下的位置,在onInterceptTouchEvent函式中獲取。
只有點選的View與當前的物件一致,並且手指按下的位置在TaskView的範圍之內,並且此時並不在分屏模式下才會監聽拖拽事件,註冊EventBus事件,並且傳送DragStartEvent事件,將當前的Task,TaskView,以及手指按下的點封裝進DragStartEvent物件中。
在拖拽TaskView對應用進行分屏過程中EventBus起到了非常重要的作用,下面先對EventBus進行分析。
EventBus事件匯流排
EventBus是一款Android下發布/訂閱事件匯流排機制,以觀察者模式實現。主要功能是替代Intent,Handler,Broadcast在Fragment,Activity,Service,執行緒之間傳遞訊息,降低了傳送者與接收者的耦合度。下面分析簡單講解如何使用EventBus,以後詳解分析實現原理。
註冊事件
通過EventBus.getDefault().register(this)來對事件進行註冊,註冊時首先通過getDefault函式獲取EventBus物件。
可以看出EventBus是通過單例模式獲得的,並且使用了主執行緒的Looper物件。
獲得EventBus物件後就可以對訂閱者進行註冊,來進行接收事件,接收事件是有優先順序的,通過引數priority控制,priority越大優先順序越高。
釋出事件
EventBus.getDefault().send(Event);
EventBus.getDefault().post(Event);
通過send或者post函式進行釋出對應的事件。
訂閱事件
通過重寫onBusEvent來訂閱事件。
言歸正傳,重新回到TaskView的onLongClick函式中,來分析EventBus例項。
TaskView註冊了Event事件,之後傳送DragStartEvent事件,DragStartEvent是EventBus.Event的子類,只有三個引數主要是用來傳輸資料。所有註冊了Event事件的觀察者,如果在onEventBus函式中的引數為DragStartEvent事件,就可以接收到傳送的DragStartEvent物件。下圖為EventBus類圖。
在RecentsView.java中attached to Window的時候對RecentsView與RecentsViewTouchHandler註冊Event事件,detached to window時登出Event事件。
RecentsViewTouchHandler的優先順序要比RecentsView的高,所以接收DragStartEvent事件較早。下面分析如何對Event事件處理,進行分屏的,整體流程見下圖。
顯示可分屏區域
在TaskView中傳送DragStartEvent事件,首先在RecentsViewTouchHandler.java的onEventBus函式接收到該事件。
在RecentsViewTouchHandler中首先清理之前的資訊,如果系統支援多視窗,並且此時不處於分屏模式,就會判斷當前的task是否支援分屏,如果不支援分屏,就會提示使用者該應用不支援分屏,如果支援分屏,根據當前系統的方向來獲取分屏的狀態。最後將獲取的分屏狀態記錄在mVisibleDockStates和mDropTargets列表中。
在getDockStatesForCurrentOrientation函式中根據Configuration判斷是平板電腦還是手機,還是橫屏豎屏。
手機裝置在橫屏狀態只允許左右分屏,在豎屏狀態只允許上下分屏,由於裝置太小了。
平板電腦在橫屏狀態可以往左邊也可以往右邊分屏,而在豎屏狀態就和手機一樣只能往上邊分。
總之,接收到事件後從event中獲取對應的Task以及TaskView,清理mDropTargets與mVisibleDockStates列表內容。只有系統支援多視窗模式,並且此時不處於分屏模式,並且分屏後的視窗大小要大於等於最小的視窗尺寸才可以進行分屏。如果應用不支援分屏就會發送ShowIncompatibleAppOverlayEvent事件,提示使用者應用不支援分屏。如果支援分屏就呼叫getDockStatesForCurrentOrientation函式獲取當前的裝置是平板還是手機,是要上下分屏還是左右分屏。
最後將獲得分屏狀態儲存在mDropTargets與mVisibleDockStates列表中。RecentsViewTouchHandler處理完DragStartEvent事件後,分發給RecentsView.java,由RecentsView進行處理。
首先呼叫updateVisibleDockRegions來更新可見的分屏區域,根據當前螢幕方向獲取分屏狀態(左右,上下分屏),此時為預設分屏狀態,dockAreaAlpha為80背景區域為灰白色,hintTextAlpha為255字型不透明。
將newDockStates轉變為ArraySet,獲取mVisibleDockStates物件,此時mVisibleDockStates有兩個物件與newDockStates中的物件一致。遍歷mVisibleDockStates中兩個物件,獲得areaAlpha為80,hintAlpha為255,下面主要計算bounds的值。
由於isDefaultDockState的值為true,所以呼叫getPreDockedBounds函式,獲得準備分屏模式的邊界區。
獲取bounds後使用動畫顯示對應區域,如下圖所示。
拖拽分屏
當螢幕顯示出來分屏區域後,擡起手指停止拖拽TaskView,在RecentsView的onTouchEvent函式中接收touch事件,
之後呼叫RecentsViewTouchHandler的handleTouchEvent處理觸控事件。最後呼叫handleTouchEvent處理。
在手指移動過程中獲取座標點evX,evY,在進行分屏時mLastDropTarget的值為null,並且currentDropTarget也為null,所以遍歷mDropTargets列表,根據前面DragStartEvent事件可以知道mDropTargets列表中是左右分屏或者上下分屏的DockState物件,就會呼叫DockState的acceptDrop函式來判斷手指移動的位置是否可以進行分屏。
TaskStack.java中的DockState實現了DropTarget介面,所以呼叫的為DockState的acceptsDrop函式,
isCurrentTarget傳入的為false,使用touchArea矩形區來根據裝置螢幕尺寸來重新計算矩形面積,根據系統邊襯區重新計算矩形邊界。最後判斷手指移動的位置是否在矩形區域內,如果在矩形範圍內返回true。由於touchArea與上面提到的dockedArea相同,所以兩者最後的矩形大小相同,也就是手指移動到上面截圖灰白矩形區,就返回true。
回到handleTouchEvent函式中,target賦值給currentDropTarget。跳出迴圈,將currentDropTarget記錄在mLastDropTarget中,將mDragTask與currentDropTarget封裝進Event事件中,傳送DragDropTargetChangedEvent給接收者。
在RecentsView中接收DragDropTargetChangedEvent事件。
根據上邊流程知道event.dropTarget為之前獲得的currentDropTarget物件,並且該物件屬於DockState型別,所以進入else語句。呼叫updateVisibleDockRegions函式更新分屏區域。該函式之前分析過主要獲得areaAlpha,hintAlpha與bounds的值,由於overrideAreaAlpha與overrideHintAlpha的值都設為-1,所以areaAlpha,hintAlpha都使用ViewState的預設值,areaAlpha為80仍為灰白色,hintAlpha為0透明。而isDefaultDockState為false呼叫getDockedBounds函式獲得bounds值。
首先根據系統配置判斷是否是水平分割螢幕,okay pad為垂直分割螢幕isHorizontalDivision為false,insets為Rect(0, 36 - 0, 0),也就是insets.left=0,inset.top=36,inset.right=0,inset.bottom=0。dividerSize為分屏後兩個螢幕之間分割條的大小,首先計算螢幕中間位置。根據分屏方向,螢幕的一半減去分割線二分之一。
根據獲得的中間位置計算新視窗的邊界。首先將newWindowBounds設定為整個視窗大小,之後再根據分屏在哪邊進行設定邊界。最後通過sanitizeStackBounds函式判斷邊界的有效性.
通過計算獲得一半的矩形視窗,使用ViewState的startAnimation函式顯示視窗,如下圖灰白色區域。
開始分屏
當螢幕顯示出來分屏區域後,擡起手指停止拖拽TaskView,在RecentsViewTouchHandler的handleTouchEvent中處理擡起事件。
將mDragTask,mTaskView,mLastDropTarget封裝進DragEndEvent事件中,傳送給註冊該事件的物件接收。由於RecentsView註冊了該事件,所以會對其進行處理。
結束拖拽後會對之前顯示的分屏區域進行隱藏,獲取SystemServiceProxy物件,呼叫startTaskInDockedMode函式,這就進入了多視窗分屏的核心程式碼中了。
在SystemServiceProxy中將建立stack的模式,以及stack的ID封裝進ActivityOptions物件中,之後呼叫AMS啟動Activity。主要在ActivityStackSupervisor物件中處理啟動應用事件,在函式startActivityFromRecentsInner中首先將傳遞的引數獲取出來記錄在activityOptions,以及launchStackId中。如果此時啟動的stack id為分屏的stack,將stack型別以及初始化邊界記錄在WMS中,延遲更新Home stack,由於需要根據分屏 Windows的邊界重新更新home stack的大小,當分屏activity啟動完成後才會對home stack重新更新計算邊界。
下面呼叫anyTaskForIdLocked函式,根據TaskId為對應任務建立stack物件。首先需要獲取此時系統中的display數量,遍歷所有display,獲取每一個display上面的stack物件,判斷一下是否對應的task已經存在某一個stack中了,如果存在就將獲取到的TaskRecord返回。
當沒有在的stack中找到對應的task,說明應用未啟動屬於冷啟動分屏,就會建立分屏stack,並將task放入stack中,啟動分屏應用。
如果已經在現有stack中獲取到的對應task,就屬於熱啟動分屏,就會將task從之前的stack中移到分屏stack中,啟動分屏應用。
冷啟動分屏
在冷啟動分屏時,此時的task不存在active list,就需要從最近任務列表中尋找,在最近任務列表中找到後就呼叫restoreRecentTaskLocked函式將該task restore到active list中。根據對應的條件重新獲得stackId,有了id後通過getStack函式獲得所需要的stack,最後將Task加入新獲得的stack中就結束了,下面主要分析getStack函式,獲取stack過程。
在getStack函式中首先根據stackId從mActivityContainers列表中獲取物件,如果activityContainer物件不為空說明需要的stack已經存在,直接返回。如果物件為空,就會將stackId,裝置號等作為引數傳遞給createStackOnDisplay函式,在特定裝置上建立stack。
在createStackOnDisplay函式建立stack過程,主要建立ActivityContainer物件,然後以stackId為key,activityContainer為value放入mActivityContainers中,通過ActivityContainer物件的attachToDisplayLocked將stack與裝置關聯,最後將mStack物件返回。在ActivityContainer的建構函式中建立ActivityStack物件mStack,這就是我們需要的stack物件。
在ActivityContainer的建構函式中主要建立物件。
獲取到stack後,呼叫attachToDisplayLocked函式將stack attach到對應的display上去。
在attachToDisplayLocked中主要做的事情為:
- 呼叫mStack的attachDisplay繼續關聯裝置
- 將mStack與裝置關聯完成後,根據onTop引數來判斷,將mStack物件新增在display的mStacks的隊頭或者隊尾。
在ActivityStack的attachDisplay函式中,核心程式碼為:
- 呼叫WMS的attachStack函式,根據stack,display獲取stack的邊界mBounds
- Resize分屏視窗。
獲取stack size
獲取分屏stack size是通過WindowManagerService的attachStack來獲得的,在該函式中首先通過displayId獲取到displayContent物件,在mStackIdToStack與displayContent中判斷是否已經存在了TaskStack物件,如果不存在就會建立一個TaskStack物件,並加入mStackIdToStack與displayContent中,之後呼叫TaskStack的attachDisplayContent函式關聯裝置,計算stack size,最後通過TaskStack的getRawBounds函式獲得stack size物件 bounds,將bounds返回給ActivityStack的mBounds物件。下面主要分析計算stack size過程。
在TaskStack物件中呼叫attachDisplayContent函式,由於此時是多視窗分屏模式,mStackId為DOCKED_STACK_ID,之後通過displayContent的內部函式獲得裝置的邏輯尺寸,並記錄在mTmpRect物件中,如果裝置沒有轉屏,螢幕尺寸為1200x825,此時mTmpRect物件的值為(0, 0, 1200, 825),由於此時dockedStack還沒有值,所以mTmpRect2為(0, 0, 0, 0),dockedOnTopOrLeft的值在SystemServiceProxy中傳入,分屏的位置。
通過getStackDockedModeBounds函式獲得dockedStack的bounds。
outBounds就為傳入的bounds物件,首先將displayRect即獲得的mTmpRect(0, 0, 1200, 825),裝置的最大尺寸賦值給bounds,如果此時是分屏的stack,再重新根據裝置尺寸,分割線divider尺寸來計算分屏後的中間位置,如果此時分屏位置在左邊,並且螢幕的寬大於高,position為600,那麼outBound.right=600, 而outBounds的值為(0,0,600,825),矩形面積為螢幕的一半。獲取到分屏後的矩形面積後,在attachDisplayContent函式中呼叫updateDisplayInfo函式更新裝置資訊。最後呼叫setBounds函式將獲得的bounds儲存在mBounds中。
TaskStack的getRawBounds函式獲得mBounds物件,最後返回到ActivityStack的mBounds中。獲取到分屏視窗的size後,就可以根據size,resize stack的大小了。
Resize視窗
當獲得分屏的stack size後,如果當前的stack為分屏的stack,就呼叫resizeDockedStackLocked函式來對分屏stack resize。首先通過getStack函式從mActivityContainers列表中取出之前建立的DOCKED STACK,也就是分屏的stack,獲取分屏stack中最頂端正在執行的ActivityRecord。之後呼叫resizeStackUncheckedLocked函式進行對分屏stack進行resize。
在resizeStackUncheckedLocked函式中如果允許更新bounds就繼續更新bounds,將DOCKED STACK中的所有task取出來,如果task可以resize的話就更新Configuration,將新的Task bounds設定給對應task,將所有task進行凍結。
最後,呼叫WMS的resizeStack函式最終對stack裡面所有的task重新計算一下尺寸,將最終獲取到的分屏size bounds設定給DOCKED STACK。
在WindowManagerService的resizeStack函式中,根據 stackId從mStackIdToStack中獲取到對應的TaskStack,然後呼叫TaskStack的setBounds來設定邊界。當設定邊界成功後,並且該stack可以被看到就進行layout。最後將stack是否全屏返回。
在TaskStack的setBounds的函式中,首先呼叫setBounds函式來為stack設定邊界,最後遍歷mTasks列表中的task物件,並且取出對應的config,對每一個task進行設定邊界。
將分屏stack的size重新計算,並且將stack中的所有task重新計算邊界後。重新回到resizeDockedStackLocked函式中繼續往下執行第九步,getStackDockedModeBounds通過分屏stack的邊界,獲取到home stack的邊界大小。
在WMS中根據stackId獲取到mStackIdToStack列表中儲存的TaskStack物件,呼叫TaskStack的getStackDockedModeBoundsLocked函式來獲得home stack邊界。
在getStackDockedModeBoundsLocked函式中根據DOCKED_STACK_ID獲取到mStackIdToStack儲存的對應dockedStack物件。
在獲取到分屏stack後,如果不忽視stack的可視性,並且分屏stack不可見,就將裝置的尺寸返回給home stack,全屏顯示。否則獲取分屏在那一側dockedSide。將Home stack所在裝置的螢幕尺寸儲存在mTmpRect中,將dockedStack的矩形尺寸儲存在mTmpRect2中,獲取此時分屏在螢幕的位置dockedOnTopOrLeft。根據getStackDockedModeBounds函式獲得分屏區之外HOME stack的矩形尺寸。
由於stackId為為HOME_STACK_ID,dockedStack為false最後根據分屏是在左側還是右側,來重新計算,上,下,左,右,邊界位置。如果分屏stack在螢幕左側佔螢幕一半,那麼Home stack就在螢幕右側佔螢幕一半。
獲取到分屏後Home Stack的尺寸後,回到第12步,遍歷系統中的所有stack,如果該stack可以被分屏stack resize,並且該stack存在,就呼叫resizeStackLocked函式。
從mActivityContainers中獲取Home Stack,在resizeStackLocked函式中呼叫resizeStackUncheckedLocked函式,然後呼叫updateBoundsAllowed函式來判斷是否需要更新邊界,由於在開始分屏時呼叫ActivityStackSupervisor的startActivityFromRecentsInner函式中,將Home stack的邊界延遲更新,設定mUpdateBoundsDeferred為true,所以在呼叫updateBoundsAllowed時就會將Home Stack邊界,記錄在mDeferredBounds中。
對分屏的stack size獲取完之後,就可以啟動Activity了。在ActivityStackSupervisor的startActivityFromRecentsInner函式的最後呼叫AMS的startActivityInPackage函式進行啟動Activity。
當分屏的ActivityTransition完成後,就會呼叫ActivityStackSupervisor的notifyAppTransitionDone函式,這時就會繼續更新HOME_STACK的邊界
根據stackid獲取Home stack,進行更新Home stack的邊界。
獲取Home stack中的mDeferredBounds,來對home stack進行更新。
相關推薦
Android7.0多視窗實現原理(一)
概述 在Android N(7.0)版本開始,系統支援了多視窗功能。在有了多視窗支援之後,使用者可以同時開啟和看到多個應用的介面。並且系統還支援在多個應用之間進行拖拽。在大螢幕裝置上,這一功能非常實用。 在Android N中多視窗有三種表現形式: 分屏模式 該模式主要
Android7.0多視窗實現原理(二)
從上文可以知道當開始分屏時從SystemUI呼叫到ActivityStackSupervisor中的startActivityFromRecentsInner函式,當要分屏的Activity已經存在了,屬於熱啟動分屏。 在anyTaskForIdLocked函式
Android7.0 多視窗特性
直接複製過來圖片不能顯示 想看圖片 請點選下面的連線 在以往的Android系統上,所有Activity都是全屏的,如果不設定透明效果,一次只能看到一個Activity介面。 但是從Android N(7.0)版本開始,系統支援了多視窗功能。在有了多視窗支援之後,使
Android7.0新特性介紹(一)——多視窗支援
Android7.0新特性 1 多視窗支援 (在手機和平板中叫分屏模式,最多就倆視窗) 在 Android N 中,Google引入了一個新的而且非常需要的多工處理功能 — 多視窗支援。 1.1 進入多視窗模式 啟動App,長按系統導航欄右
c++多態實現原理
c++編譯器 anim 被調用 虛指針 編譯 基類 綁定 確定調用 包括 C++的多態性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類
反射和多型實現原理詳解
Table of Contents 反射和多型 多型 多型的定義和用法 多型的實現原理 反射 反射的實現原理 反射的應用 反射的弊端 反射和多型 這兩種技術並無直接聯絡,之所以把它們放在一起說,是因為Java提供讓我們在執行時識別物件和類的資訊,主要有
淺談C++多型實現原理(虛繼承的奧祕)
大夥都知道,如果要實現C++的多型,那麼,基類中相應的函式必須被宣告為虛擬函式(或純虛擬函式)。舉個例子: class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) { } virtual fl
Mybatis 實現原理 一
1.引入demomybatis-config檔案內容<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Con
Android7.0 Settings主選單新增一條item
往Android7.0的settings的主選單裡新增一條item. N與M的Settings不同,在M上只要找到Settings裡面的相應的佈局新增就好,但在N上由於Setting裡面的item不再受佈局控制,所以得在程式碼中新增. 找到/packages
多型實現原理剖析
1. 虛擬函式表 C++的多型是通過一張虛擬函式表(virtual Table)來實現的,簡稱為V-Table,(這個表是隱式的,不需要關心其生成與釋放)在這個表中,主要是一個類的虛擬函式的地址表,這張表解決了繼承,覆寫的問題,保證其真實反應實際的函式,這樣,在有虛擬函式的類的例項中這個表被分配在了這個例項
C++(多型實現原理)函式重寫,過載,重定義
多型的實現原理: 首先介紹下函式重寫 重定義 過載的區別; 函式重寫: 發生在父類和子類之間,子類將父類中的同名函式進行了覆蓋,如果在函式前面含有virtual那麼就是重寫,如果沒有就成了覆蓋,子類的同名函式將會覆蓋(隱藏)父類的同名
是時候來了解android7了:多視窗支援
本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。 這篇文章開始, 我們來了解一下android 7的一些新特性, 話說今年android 7預覽版本來的比以往都稍早一些, 這樣對於我們開發者來說算是一個好訊息, 我
Android N7.0多視窗適配開發指導
1、設定resizeableActivity為false <activityandroid:name="com.exmaple.Activity2" android:resizeableActivity="false 2、Activity1啟動Activity2時,
淺析多型實現原理
實現過程 當我們在宣告一個類時,編譯器會自動幫我們建立一個虛擬函式表。 比如下面的這段程式碼: 編譯器為我們生成的虛擬函式表 虛擬函式表: 虛擬函式表是由編譯器自動產生的一種儲存類成員函式的一種資料結構。其中虛擬函式會被自動放入表中。 那編譯器是怎
C++多型實現原理
理論知識: 當類中宣告虛擬函式時,編譯器會在類中生成一個虛擬函式表。 虛擬函式表是一個儲存類成員函式指標的資料結構。 虛擬函式表是由編譯器自動生成與維護的。 virtual成員函式會被編譯器放入虛擬函式表中。 當存在虛擬函式時,每個物件中都有一個指向虛擬函式表的指標
淺析 Linux 中的時間程式設計和實現原理一—— Linux 應用層的時間程式設計
簡介: 本文試圖完整地描述 Linux 系統中 C 語言程式設計中的時間問題。主要內容包括應用程式中的時間程式設計方法;時鐘硬體簡介;Glibc 時間函式的實現以及 Linux 核心對時間的支援和實現原理。這是第 1 部分,探討應用開發中的時間程式設計問題。 引子 我們都
java反射機制的實現原理 (一)
反射機制:所謂的反射機制就是java語言在執行時擁有一項自觀的能力。通過這種能力可以徹底的瞭解自身的情況為下一步的動作做準備。下面具體介紹一下java的反射機制。這裡你將顛覆原來對java的理解。 Java的反射機制的實現要藉助於4個類:class,Constructo
展訊7731C_M Android6.0 充電指示燈實現(一)------關機充電實現
轉載:https://blog.csdn.net/xiaopangzi313/article/details/52194513前言: 在手機充電中常常使用充電指示燈來觀察手機充電狀態,比如說將手機插上USB線充電時指示燈會亮,如果拔出USB,指示燈會滅,
原始碼分析 Alibaba sentinel 滑動視窗實現原理(文末附原理圖)
要實現限流、熔斷等功能,首先要解決的問題是如何實時採集服務(資源)呼叫資訊。例如將某一個介面設定的限流闊值 1W/tps,那首先如何判斷當前的 TPS 是多少?Alibaba Sentinel 採用滑動視窗來實現實時資料的統計。 > 溫馨提示:如果對原始碼不太感興趣,可以先跳到文末,看一下滑動視窗的設
深入理解多執行緒(一)——Synchronized的實現原理
synchronized,是Java中用於解決併發情況下資料同步訪問的一個很重要的關鍵字。當我們想要保證一個共享資源在同一時間只會被一個執行緒訪問到時,我們可以在程式碼中使用synchronized關鍵字對類或者物件加鎖。那麼,本文來介紹一下synchronized關鍵字的實