Android之Window與WindowManager

?Window表示一個窗口的概念,在日常開發中直接接觸Window的機會并不多,但卻會經常用到Window,activitytoastdialogPopupWindow、狀態欄等都是Window。在Android中Window是個抽象類,并且僅有一個實現類PhoneWindow

1、Window

?Android中,Window有應用Window、子Window及系統Window三種類型,分別對應不同的層級范圍,層級越高,顯示越靠前,這里的“靠前”是指層級大的Window會覆蓋在層級小的Window上面。

  • 應用Window:對應層級范圍是1~99,每個activity就對應一個應用Window,如果在activity中創建了一個應用Window,那么當跳轉到另外一個Activity時,該Window會被覆蓋。應用Window的高度不受狀態欄影響。
  • 子Window:對應層級范圍是1000~1999,PopupWindow默認就是一個子Window(可以修改PopupWindow的Window類型),如果在activity中創建了一個子Window,那么當跳轉到另外一個Activity時,該Window也會被覆蓋。子Window的高度受狀態欄影響。
  • 系統Window:對應層級范圍是2000~2999,toast、狀態欄等都是系統Window,如果創建了一個系統Window,那么只有當該應用被銷毀時,該Window才被會關閉(排除主動關閉),所以可以用系統Window實現像360那樣的懸浮小球。系統Window需要設置<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />權限,否則會拋異常,在6.0以上需要動態申請。系統Window的高度不受狀態欄影響。

?前面說了Window的層級,下面就來看一個示例。

    //代碼參考了PopupWindow的源代碼。private void startWindow() {//拿到activity中的wm對象,在attach中創建,是一個WindowManagerImpl對象wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);frame = new PopupDecorView(this);frame.setLayoutParams(new ActivityzhoLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));View view = View.inflate(this, R.layout.window_layout, null);Button bt = view.findViewById(R.id.window_layout_button);bt.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {dismiss();}});//重新設置WindowManager.LayoutParams的值WindowManager.LayoutParams p = createPopupLayoutParams(frame.getWindowToken());frame.addView(view);wm.addView(frame, p);}private LayoutParams createPopupLayoutParams(IBinder windowToken) {final WindowManager.LayoutParams p = new WindowManager.LayoutParams();//設置Window gravity。gravity 表示居中,top表示位于頂部p.gravity = Gravity.CENTER|Gravity.TOP;p.flags = computeFlags(p.flags);//設置Window的類型,其實這里我們也可以設置1~99、1000~1999、2000~2999之間的任意數字p.type = LayoutParams.TYPE_APPLICATION;//設置Window Tokenp.token = windowToken;//設置輸入法模式p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;//設置Window動畫p.windowAnimations = 0;//設置Window像素格式p.format = PixelFormat.TRANSLUCENT;// Used for debugging.p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));//設置Window寬p.width = LayoutParams.MATCH_PARENT;//設置Window高p.height = LayoutParams.WRAP_CONTENT;return p;}private int computeFlags(int curFlags) {curFlags &= ~(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |WindowManager.LayoutParams.FLAG_SPLIT_TOUCH);curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;return curFlags;}//關閉Windowprivate void dismiss() {wm.removeView(frame);}private class PopupDecorView extends FrameLayout {public PopupDecorView(Context context) {super(context);}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {if (getKeyDispatcherState() == null) {return super.dispatchKeyEvent(event);}if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {final KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null) {state.startTracking(event, this);}return true;} else if (event.getAction() == KeyEvent.ACTION_UP) {final KeyEvent.DispatcherState state = getKeyDispatcherState();if (state != null && state.isTracking(event) && !event.isCanceled()) {dismiss();return true;}}return super.dispatchKeyEvent(event);} else {return super.dispatchKeyEvent(event);}}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {
//	            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
//	                return true;
//	            }return super.dispatchTouchEvent(ev);}}
復制代碼

?WindowManager.LayoutParams用于描述Window的參數,關于其詳細參數可以參考Android Activity應用窗口的創建過程分析這篇文章。 ?先來看WindowManager.LayoutParams的參數type,也就是Window類型。下面來看不同Window類型的顯示效果

系統Window及應用Window顯示效果
子Window顯示效果

?粉色部分就是創建的Window,可以看出系統及應用Window不受狀態欄影響,而子Window卻因為狀態欄導致按鈕超出Window范圍。所以可以認為子Window的高度被被狀態欄占去一部分,而其他類型Window則不受此影響,讓WIndow居中時,子Window在手機中的位置也會比其他類型Window的位置高一些,這里就不驗證了,至于子Window為什么在狀態欄的下面,那是因為狀態欄的層級比子Window層級要高。

?WindowManager.LayoutParamsflags也是一個非常重要的參數,由于類型比較多,這里就主要介紹以下幾個類型。

  • FLAG_NOT_TOUCH_MODAL:在此模式下,系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域內的單擊事件則自己處理。一般都需要開啟此標記
  • FLAG_NOT_FOCUSABLE:在此模式下,Window不能獲取焦點,也不能接受各種輸入事件,此標記會同時開啟FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層的具有焦點的Window。所以如果Window中有EditText等輸入控件時,就不應該啟用此標記。
  • FLAG_SHOW_WHEN_LOCKED:開啟此模式可以讓Window顯示在鎖屏的界面。

?WindowManager.LayoutParams中比較常用的參數就上面兩個,當然也可以設置Window的寬高、動畫、token等等,這里就不一一敘述了。 ?從上面示例可以看出,Window并不實際存在,它是以一個View的形式展示在屏幕上。

2、WindowManager

?WindowManager的主要功能是提供簡單的API使得使用者可以方便地將一個View作為一個窗口添加到系統中,它是一個接口,繼承自ViewManager接口,ViewManager接口比較簡單,只有以下三個方法。

public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}
復制代碼

?從方法名也可以看出對Window的增刪改就是針對View的增刪改。方法雖然只有三個,但已經完全夠用了。WindowManager的具體實現是WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();private final Context mContext;//父Windowprivate final Window mParentWindow;private IBinder mDefaultToken;...//添加View@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}//更新View@Overridepublic void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);}...//異步移除View@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}//同步移除View@Overridepublic void removeViewImmediate(View view) {mGlobal.removeView(view, true);}...
}
復制代碼

?這里采用了代理模式,將所有操作交給WindowManagerGlobal來執行。首先來看Window的添加。

2.1、添加Window

?在前面的例子中可以看到,創建一個Window就是向WindowManagerImpl中添加一個View,而WindowManagerImpl又將操作交給了WindowManagerGlobal來處理,下面就來看看WindowManagerGlobaladdView的實現。

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {//檢查參數if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}//拿到Window的寬高、type等布局參數final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;...ViewRootImpl root;View panelParentView = null;synchronized (mLock) {...//查找View是否已經存在,WindowManager不允許同一個View被添加兩次int index = findViewLocked(view, false);if (index >= 0) {//如果View已在被銷毀的列表中,那么就先銷毀列表中存在的Viewif (mDyingViews.contains(view)) {// Don't wait for MSG_DIE to make it's way through root's queue.mRoots.get(index).doDie();} else {//很常見的一個異常,表示不能重復添加同一Viewthrow new IllegalStateException("View " + view+ " has already been added to the window manager.");}// The previous removeView() had not completed executing. Now it has.}//如果是子Window則需要先找到它的父Viewif (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {final int count = mViews.size();for (int i = 0; i < count; i++) {if (mRoots.get(i).mWindow.asBinder() == wparams.token) {panelParentView = mViews.get(i);}}}//創建一個新的ViewRootImplroot = new ViewRootImpl(view.getContext(), display);//給View設置參數view.setLayoutParams(wparams);//保存ViewmViews.add(view);//保存ViewRootImplmRoots.add(root);//保存參數mParams.add(wparams);//繪制View、添加Windowtry {// 將作為窗口的控件設置給ViewRootImpl。這個動作將導致ViewRootImpl向WMS添加新的窗口、申請Surface以及托管控件在Surface上的重繪動作。這才是真正意義上完成了窗口的添加操作root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}
復制代碼

?在addView方法中主要做了參數檢查、查找子Window的父View、創建ViewRootImpl對象并通過ViewRootImplsetView方法來實現View的繪制及Window添加操作。下面來看ViewRootImplsetView方法的實現。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {//保存當前ViewmView = view;...//保存參數attrs = mWindowAttributes;...//繪制View。requestLayout();...try {...res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);} catch (RemoteException e) {....} finally {if (restore) {attrs.restore();}}...//添加失敗if (res < WindowManagerGlobal.ADD_OKAY) {mAttachInfo.mRootView = null;//添加失敗mAdded = false;mFallbackEventHandler.setView(null);unscheduleTraversals();setAccessibilityFocus(null, null);//返回錯誤的原因,相比很多錯誤信息大家都會遇到過switch (res) {//token出錯case WindowManagerGlobal.ADD_BAD_APP_TOKEN:case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not valid; is your activity running?");case WindowManagerGlobal.ADD_NOT_APP_TOKEN:throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not for an application");case WindowManagerGlobal.ADD_APP_EXITING:throw new WindowManager.BadTokenException("Unable to add window -- app for token " + attrs.token+ " is exiting");//添加Window已存在case WindowManagerGlobal.ADD_DUPLICATE_ADD:throw new WindowManager.BadTokenException("Unable to add window -- window " + mWindow+ " has already been added");case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:// Silently ignore -- we would have just removed it// right away, anyway.return;case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:throw new WindowManager.BadTokenException("Unable to add window "+ mWindow + " -- another window of type "+ mWindowAttributes.type + " already exists");//未申請權限,當創建系統Window時是需要申請權限的case WindowManagerGlobal.ADD_PERMISSION_DENIED:throw new WindowManager.BadTokenException("Unable to add window "+ mWindow + " -- permission denied for window type "+ mWindowAttributes.type);case WindowManagerGlobal.ADD_INVALID_DISPLAY:throw new WindowManager.InvalidDisplayException("Unable to add window "+ mWindow + " -- the specified display can not be found");//window類型未在1~99,1000~1999,2000~2999這個范圍內。case WindowManagerGlobal.ADD_INVALID_TYPE:throw new WindowManager.InvalidDisplayException("Unable to add window "+ mWindow + " -- the specified window type "+ mWindowAttributes.type + " is not valid");}throw new RuntimeException("Unable to add window -- unknown error code " + res);}...}}}
復制代碼

?該方法真正意義上完成了View的繪制及Window的添加操作,來看requestLayoutmWindowSession.addToDisplay這兩個方法。前者主要是申請Surface以及托管控件在Surface上的重繪動作,即View的測量、布局、繪制流程。關于該方法詳細內容可以參考Android源碼分析之View繪制流程、《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系統這兩篇文章。后者主要向WindowManagerService(WMS)添加新的窗口。 ?總體來說,WindowManagerGlobal通過父窗口調整了布局參數之后,將新建的ViewRootImpl、控件以及布局參數保存在mRootsmViewsmParams這三個數組中,然后將View交給新建的ViewRootImpl進行處理,從而完成了窗口的添加。 ?WindowManagerGlobal管理窗口的原理如下圖所示。

來自于《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系統
2.2、更新Window

?相對于添加Window,更新Window就簡單很多了,主要是修改布局參數,然后調用ViewRootImpl.setLayoutParams來更新View。

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//修改view的布局參數view.setLayoutParams(wparams);synchronized (mLock) {int index = findViewLocked(view, true);//查找view對應的ViewRootImplViewRootImpl root = mRoots.get(index);//移除舊的布局參數mParams.remove(index);//添加新的布局參數mParams.add(index, wparams);//更新布局參數root.setLayoutParams(wparams, false);}}復制代碼

?代碼還是比較簡單的,下面就來看ViewRootImplsetLayoutParams方法的實現。

    void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {synchronized (this) {//修改布局參數的操作...//對View進行重新測量、布局、繪制mWindowAttributesChanged = true;scheduleTraversals();}}
復制代碼

?該方法也比較簡單,主要就是調用scheduleTraversals方法來對View進行重新測量、布局及繪制。scheduleTraversals在這里就不詳細講解了,在View的繪制流程中已經講解的很清楚了。 ?總體上來說,Window的更新操作就是對View的重新測量、布局及繪制。

2.2、關閉Window

?關閉Window調用的是WindowManagerGlobalremoveView方法。

    public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}}//移除Viewprivate void removeViewLocked(int index, boolean immediate) {ViewRootImpl root = mRoots.get(index);View view = root.getView();if (view != null) {//拿到輸入法管理InputMethodManager imm = InputMethodManager.getInstance();if (imm != null) {//關閉輸入法Windowimm.windowDismissed(mViews.get(index).getWindowToken());}}//返回true表示異步刪除,false表示同步刪除boolean deferred = root.die(immediate);if (view != null) {view.assignParent(null);if (deferred) {//異步刪除只是將view添加到mDyingViews這個集合即可。mDyingViews.add(view);}}}//該方法在ViewRootImpl中boolean die(boolean immediate) {//立即移除Viewif (immediate && !mIsInTraversal) {doDie();return false;}...//異步移除View,mHandler.sendEmptyMessage(MSG_DIE);return true;}
復制代碼

?最終還是通過ViewRootImpl來實現的Window的關閉,immediatetrue時則代表立即刪除當前Window的信息及資源釋放,否則異步執行。當異步移除View時,也是調用了ViewRootImpldoDie方法,只不過異步需要排隊而已。

    void doDie() {//如果在非UI線程則報錯checkThread();...synchronized (this) {if (mRemoved) {return;}mRemoved = true;if (mAdded) {//資源釋放dispatchDetachedFromWindow();}if (mAdded && !mFirst) {destroyHardwareRenderer();...}mAdded = false;}//從mRoots、mViews及mParams這三個數組中移除信息WindowManagerGlobal.getInstance().doRemoveView(this);}
復制代碼

?在該方法里主是調用dispatchDetachedFromWindow進行資源釋放,在dispatchDetachedFromWindow中會釋放Surface所占內存、從WMS中移除Window、停止動畫、線程等。最后刷新WindowManagerGlobalmRootsmViewsmParams這三個數組的數據。 ?當調用ViewRootImpldoDie方法后,該ViewRootImpl也就完成了自己的使命了,等待被GC回收。因此可以得出這樣一個結論:ViewRootImpl的生命從setView()開始,到die()結束。

3、總結

?到這里,相必對WIndow及WindowManager就有了較深入的了解,主要總結以下幾點。

  • Window分為應用Window、子Window及系統Window,不同類型的Window對應著不同的層級范圍,層級越高,顯示越靠前。
  • 子Window的高度受狀態欄的影響。而系統Window及應用Window則無此限制,所以實現一個子Window需要考慮狀態欄的高度
  • 一個Window對應著一個ViewRootImpl,也就是說ViewRootImpl與Window同生共死。
  • Window的更新其實對View的重新執行測量、布局及繪制。

【參考資料】 《Android藝術探索》 Android Activity應用窗口的創建過程分析 Android Window 機制探索 《深入理解Android 卷III》第四章 深入理解WindowManagerService 《深入理解Android 卷III》第六章 深入理解控件(ViewRoot)系統 [深入理解Android卷一全文-第八章]深入理解Surface系統

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280024.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280024.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280024.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# WPF This用法詳解(經典)

概述this在C#中有多種用法&#xff0c;也比較常見&#xff0c;這節主要針對它常用的四種用法展開講解.用法1:構造函數串聯執行;用法2:通過this區分傳參和類中全局的定義;用法3:方法擴展類;用法4:將對象作為參數傳遞;代碼實例using System.Text;namespace Caliburn.Micro.Hello.…

前端node 和vue開發之環境搭建

下載nvm nodejs 的快捷鍵是配置后自動生成的 nvm 的 setting.txt配置 root: C:\dev\nvmpath: C:\dev\nodejsarch: 32proxy: root指向 nvm.exeroot: C:\dev\nvmpath: C:\dev\nodejs 配置環境變量 變量名 變量值 GIT_HOME C:\dev…

如何從特定位置開始分享YouTube視頻

Tech tutorials that start with 3 minutes of “hey guys what’s up” are the worst. Get to the point! Here’s how you can bypass that nonsense when sharing a video with your friends. 最糟糕的是從3分鐘的“嗨&#xff0c;大家好起來”開始的技術教程。 講到重點&a…

解決git提交問題error: The requested URL returned error: 403 Forbidden while accessing

2019獨角獸企業重金招聘Python工程師標準>>> git提交代碼時&#xff0c;出現這個錯誤“error: The requested URL returned error: 403 Forbidden while accessing https” 解決方法&#xff1a; 編輯.git目錄下的config文件即可。 vim .git/config [core] …

基于.NetCore開發博客項目 StarBlog - (24) 統一接口數據返回格式

1前言開發接口&#xff0c;是給客戶端&#xff08;Web前端、App&#xff09;用的&#xff0c;前面說的RESTFul&#xff0c;是接口的規范&#xff0c;有了統一的接口風格&#xff0c;客戶端開發人員在訪問后端功能的時候能更快找到需要的接口&#xff0c;能寫出可維護性更高的代…

如何將C# 7類庫升級到C# 8?使用可空引用類型

這篇文章將介紹將C# 7類庫升級到C# 8&#xff08;支持可空引用類型&#xff09;的一個案例。本案例中使用的項目Tortuga Anchor由一組MVVM風格的基類、反射代碼和各種實用程序函數組成。之所以選擇這個項目&#xff0c;是因為它很小&#xff0c;并且同時包含了慣用和不常用的C#…

android 設備名稱_如何更改您的Android TV的設備名稱

android 設備名稱Android TV is Google’s attempt at taking over the living room, and with some units being available for under $99, it’s not unheard of for users to have more than one box. The problem is, when multiple devices identify themselves identical…

AD-查找符合指定條件的用戶Get-User

以下服務器為Exchange 2010一、使用 Get-User 命令查找部門為IT的用戶Get-User -ResultSize Unlimited | ? { $_.Department -Eq "IT" } | ft Name,Department二、查找注釋為多行內容的指定用戶如下圖&#xff1a;注釋Notes信息為多行要使用 match 和 (?*) 來做匹配…

目標檢測算法之Fast R-CNN算法詳解

在介紹Fast R-CNN之前我們先介紹一下SPP Net 一、SPP Net SPP&#xff1a;Spatial Pyramid Pooling&#xff08;空間金字塔池化&#xff09; 眾所周知&#xff0c;CNN一般都含有卷積部分和全連接部分&#xff0c;其中&#xff0c;卷積層不需要固定尺寸的圖像&#xff0c;而全連…

RGB-D(深度圖像) 圖像深度

RGB-D&#xff08;深度圖像&#xff09; 深度圖像 普通的RGB三通道彩色圖像 Depth Map 在3D計算機圖形中&#xff0c;Depth Map&#xff08;深度圖&#xff09;是包含與視點的場景對象的表面的距離有關的信息的圖像或圖像通道。其中&#xff0c;Depth Map 類似于灰度圖像&…

WPF-21 基于MVVM員工管理-01

接下來我們通過兩節課程使用MVVM來開發一個簡單的Demo&#xff0c;首先我們創建一個項目名稱WPF-22-MVVM-Demo&#xff0c;目錄結構如下&#xff1a;我們在Models文件下創建Employee類并讓該類實現INotifyPropertyChanged接口&#xff0c;該類中定義編號、姓名和角色三個基本屬…

qt 蘋果應用程序_什么是蘋果的電視應用程序,您應該使用它嗎?

qt 蘋果應用程序Apple’s TV app, which recently appeared on iOS devices and Apple TV, is meant to help users discover and watch shows across an increasingly expanding lineup of television channels, as well as iTunes movies and shows, in one central app. App…

細說flush、ob_flush的區別

ob_flush/flush在手冊中的描述, 都是刷新輸出緩沖區, 并且還需要配套使用, 所以會導致很多人迷惑… 其實, 他們倆的操作對象不同, 有些情況下, flush根本不做什么事情.. ob_*系列函數, 是操作PHP本身的輸出緩沖區. 所以, ob_flush是刷新PHP自身的緩沖區. 而flush, 嚴格來講, 這…

關于jHipster框架在構建中的出現的error修復

jhipster The JDL object and the database type are both mandatory.這個錯誤應該是在構建基于jHipster的spring-cloud項目中經常遇到的&#xff0c;因為這個在這個過程中會讀取.yo-rc文件&#xff0c;之后生成相關的.json文件&#xff0c;再之后生成相關的.java文件&#xff…

protobuf編碼

proto2Protocol Buffers 是一種輕便高效的結構化數據存儲格式&#xff0c;可以用于結構化數據序列化&#xff0c;適合做數據存儲或 RPC 數據交換格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。 字段規則 required: 字段必須存在opti…

定制.NET 6.0的Middleware中間件

大家好&#xff0c;我是張飛洪&#xff0c;感謝您的閱讀&#xff0c;我會不定期和你分享學習心得&#xff0c;希望我的文章能成為你成長路上的墊腳石&#xff0c;讓我們一起精進。在本文中&#xff0c;我們將學習中間件&#xff0c;以及如何使用它進一步定制應用程序。我們將快…

Python-循環控制--個人課堂筆記

Python中的兩種循環方式&#xff08;目前學到&#xff09;&#xff1a;for循環和while循環 for循環和while循環的區別&#xff1a; for循環一般用于控制循環的次數&#xff0c;while循環則是條件循環。 操作實例-猜數字小游戲&#xff08;3次猜錯提示游戲結束&#xff09;&…

刪除microsoft_如何從您的Microsoft帳戶中刪除設備

刪除microsoftWhen you sign into Windows 8 or 10 using your Microsoft account (and other Microsoft devices, like an Xbox), those devices become associated with your account. If you want to remove an old device you’ve gotten rid of, you’ll have to pay a vi…

線程的語法 (event,重要)

Python threading模塊 2種調用方式 直接調用 12345678910111213141516171819import threadingimport timedef sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3)if __name__ __main__:t1 threading.Thread(targetsayhi,args(…

求最大值和下標值

本題要求編寫程序&#xff0c;找出給定的n個數中的最大值及其對應的最小下標&#xff08;下標從0開始&#xff09;。 輸入格式: 輸入在第一行中給出一個正整數n&#xff08;1<n≤10&#xff09;。第二行輸入n個整數&#xff0c;用空格分開。 輸出格式: 在一行中輸出最大值及…