導航:首頁 > 操作系統 > androidview分析

androidview分析

發布時間:2024-02-29 03:16:05

android 自定義View之Layout過程

系列文章:

在上篇文章: Android 自定義View之Measure過程 ,我們分析了Measure過程,本次將會掀開承上啟下的Layout過程神秘面紗,
通過本篇文章,你將了解到:

在上篇文章的比喻里,我們說過:

該ViewGroup 重寫了onMeasure(xx)和onLayout(xx)方法:

同時,當layout 執行結束,清除PFLAG_FORCE_LAYOUT標記,該標記會影響Measure過程是否需要執行onMeasure。

該View 重寫了onMeasure(xx)和onLayout(xx)方法:

MyViewGroup里添加了MyView、Button兩個控制項,最終運行的效果如下:

可以看出,MyViewGroup 里子布局的是橫向擺放的。我們重點關注Layout過程。實際上,MyViewGroup里我們只重寫了onLayout(xx)方法,MyView也是重寫了onLayout(xx)方法。
接下來,分析View Layout過程。

與Measure過程類似,連接ViewGroup onLayout(xx)和View onLayout(xx)之間的橋梁是View layout(xx)。

可以看出,最終都調用了setFrame(xx)方法。

對於Measure過程在onMeasure(xx)里記錄了尺寸的值,而對於Layout過程則在layout(xx)里記錄了坐標值,具體來說是在setFrame(xx)里,該方法兩個重點地方:

View.onLayout(xx)是空實現
從layout(xx)和onLayout(xx)聲明可知,這兩個方法都是可以被重寫的,接下來看看ViewGroup是否重寫了它們。

ViewGroup.layout(xx)雖然重寫了layout(xx),但是僅僅做了簡單判斷,最後還是調用了View.layout(xx)。

這重寫後將onLayout變為抽象方法,也就是說繼承自ViewGroup的類必須重寫onLayout(xx)方法。
我們以FrameLayout為例,分析其onLayout(xx)做了什麼。

FrameLayout.onLayout(xx)為子布局Layout的時候,起始坐標都是以FrameLayout為基準,並沒有記錄上一個子布局佔了哪塊位置,因此子布局的擺放位置可能會重疊,這也是FrameLayout布局特性的由來。而我們之前的Demo在水平方向上記錄了上一個子布局的擺放位置,下一個擺放時只能在它之後,因此就形成了水平擺放的功能。
由此類推,我們常說的某個子布局在父布局裡的哪個位置,決定這個位置的即是ViewGroup.onLayout(xx)。

上邊我們分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),這四者什麼關系呢?
View.layout(xx)

View.onLayout(xx)

ViewGroup.layout(xx)

ViewGroup.onLayout(xx)

View/ViewGroup 子類需要重寫哪些方法:

用圖表示:

通過上述的描述,我們發現Measure過程和Layout過程里定義的方法比較類似:

它倆的套路比較類似:measure(xx)、layout(xx)一般不需要我們重寫,measure(xx)里調用onMeasure(xx),layout(xx)為調用者設置坐標值。
若是ViewGroup:onMeasure(xx)里遍歷子布局,並測量每個子布局,最後將結果匯總,設置自己測量的尺寸;onLayout(xx)里遍歷子布局,並設置每個子布局的坐標。
若是View:onMeasure(xx)則測量自身,並存儲測量尺寸;onLayout(xx)不需要做什麼。

Measure過程雖然比Layout過程復雜,但仔細分析後就會發現其本質就是為了設置兩個成員變數:

而Layout過程雖然比較簡單,其本質是為了設置坐標值

將Measure設置的變數和Layout設置的變數聯系起來:

此外,Measure過程通過設置PFLAG_LAYOUT_REQUIRED 標記來告訴需要進行onLayout,而Layout過程通過清除 PFLAG_FORCE_LAYOUT來告訴Measure過程不需要執行onMeasure了。
這就是Layout的承上作用

我們知道View的繪制需要依靠Canvas繪制,而Canvas是有作用區域限制的。例如我們使用:

Cavas繪制的起點是哪呢?
對於硬體繪制加速來說:正是通過Layout過程中設置的RenderNode坐標。
而對於軟體繪制來說:

關於硬體繪制加速/軟體繪制 後續文章會分析。

這就是Layout的啟下作用
以上即是Measure、Layout、Draw三者的內在聯系。
當然Layout的"承上"還需要考慮margin、gravity等參數的影響。具體用法參見最開始的Demo。

getMeasuredWidth()/getMeasuredHeight 與 getWidth/getHeight區別
我們以獲取width為例,分別來看看其方法:

getMeasuredWidth():獲取測量的寬,屬於"臨時值"
getWidth():獲取View真實的寬
在Layout過程之前,getWidth() 默認為0
何時可以獲取真實的寬、高

下篇將分析Draw()過程,我們將分析"一切都是draw出來的"道理

本篇基於 Android 10.0

㈡ android的自定義View的實現原理哪位能給我個思路呢。謝謝。

如果說要按類型來劃分的話,自定義View的實現方式大概可以分為三種,自繪控制項、組合控制項、以及繼承控制項。那麼下面我們就來依次學習一下,每種方式分別是如何自定義View的。

一、自繪控制項

自繪控制項的意思就是,這個View上所展現的內容全部都是我們自己繪制出來的。繪制的代碼是寫在onDraw()方法中的,而這部分內容我們已經在Android視圖繪制流程完全解析,帶你一步步深入了解View(二)中學習過了。

下面我們准備來自定義一個計數器View,這個View可以響應用戶的點擊事件,並自動記錄一共點擊了多少次。新建一個CounterView繼承自View,代碼如下所示:

<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffcb05">

<Button
android:id="@+id/button_left"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="@drawable/back_button"
android:text="Back"
android:textColor="#fff"/>

<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="ThisisTitle"
android:textColor="#fff"
android:textSize="20sp"/>

</RelativeLayout>

在這個布局文件中,我們首先定義了一個RelativeLayout作為背景布局,然後在這個布局裡定義了一個Button和一個TextView,Button就是標題欄中的返回按鈕,TextView就是標題欄中的顯示的文字。

接下來創建一個TitleView繼承自FrameLayout,代碼如下所示:

{

privateButtonleftButton;

privateTextViewtitleText;

publicTitleView(Contextcontext,AttributeSetattrs){
super(context,attrs);
LayoutInflater.from(context).inflate(R.layout.title,this);
titleText=(TextView)findViewById(R.id.title_text);
leftButton=(Button)findViewById(R.id.button_left);
leftButton.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
((Activity)getContext()).finish();
}
});
}

publicvoidsetTitleText(Stringtext){
titleText.setText(text);
}

publicvoidsetLeftButtonText(Stringtext){
leftButton.setText(text);
}

(OnClickListenerl){
leftButton.setOnClickListener(l);
}

}

TitleView中的代碼非常簡單,在TitleView的構建方法中,我們調用了LayoutInflater的inflate()方法來載入剛剛定義的title.xml布局,這部分內容我們已經在Android LayoutInflater原理分析,帶你一步步深入了解View(一)這篇文章中學習過了。

接下來調用findViewById()方法獲取到了返回按鈕的實例,然後在它的onClick事件中調用finish()方法來關閉當前的Activity,也就相當於實現返回功能了。

另外,為了讓TitleView有更強地擴展性,我們還提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分別用於設置標題欄上的文字、返回按鈕上的文字、以及返回按鈕的點擊事件。

到了這里,一個自定義的標題欄就完成了,那麼下面又到了如何引用這個自定義View的部分,其實方法基本都是相同的,在布局文件中添加如下代碼:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.example.customview.TitleView
android:id="@+id/title_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.customview.TitleView>

</RelativeLayout>

這樣就成功將一個標題欄控制項引入到布局文件中了,運行一下程序。

現在點擊一下Back按鈕,就可以關閉當前的Activity了。如果你想要修改標題欄上顯示的內容,或者返回按鈕的默認事件,只需要在Activity中通過findViewById()方法得到TitleView的實例,然後調用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法進行設置就OK了。

㈢ Android - View 繪制流程

我們知道,在 Android 中,View 繪制主要包含 3 大流程:

Android 中,主要有兩種視圖: View 和 ViewGroup ,其中:

雖然 ViewGroup 繼承於 View ,但是在 View 繪制三大流程中,某些流程需要區分 View 和 ViewGroup ,它們之間的操作並不完全相同,比如:

對 View 進行測量,主要包含兩個步驟:

對於第一個步驟,即求取 View 的 MeasureSpec ,首先我們來看下 MeasureSpec 的源碼定義:

MeasureSpec 是 View 的一個公有靜態內部類,它是一個 32 位的 int 值,高 2 位表示 SpecMode(測量模式),低 30 位表示 SpecSize(測量尺寸/測量大小)。
MeasureSpec 將兩個數據打包到一個 int 值上,可以減少對象內存分配,並且其提供了相應的工具方法可以很方便地讓我們從一個 int 值中抽取出 View 的 SpecMode 和 SpecSize。

一個 MeasureSpec 表達的是:該 View 在該種測量模式(SpecMode)下對應的測量尺寸(SpecSize)。其中,SpecMode 有三種類型:

對 View 進行測量,最關鍵的一步就是計算得到 View 的 MeasureSpec ,子View 在創建時,可以指定不同的 LayoutParams (布局參數), LayoutParams 的源碼主要內容如下所示:

其中:

LayoutParams 會受到父容器的 MeasureSpec 的影響,測量過程會依據兩者之間的相互約束最終生成子View 的 MeasureSpec ,完成 View 的測量規格。

簡而言之,View 的 MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同決定( DecorView 的 MeasureSpec 是由自身的 LayoutParams 和屏幕尺寸共同決定,參考後文)。也因此,如果要求取子View 的 MeasureSpec ,那麼首先就需要知道父容器的 MeasureSpec ,層層逆推而上,即最終就是需要知道頂層View(即 DecorView )的 MeasureSpec ,這樣才能一層層傳遞下來,這整個過程需要結合 Activity 的啟動過程進行分析。

我們知道,在 Android 中, Activity 是作為視圖組件存在,主要就是在手機上顯示視圖界面,可以供用戶操作, Activity 就是 Andorid 中與用戶直接交互最多的系統組件。

Activity 的基本視圖層次結構如下所示:

Activity 中,實際承載視圖的組件是 Window (更具體來說為 PhoneWindow ),頂層View 是 DecorView ,它是一個 FrameLayout , DecorView 內部是一個 LinearLayout ,該 LinearLayout 由兩部分組成(不同 Android 版本或主題稍有差異): TitleView 和 ContentView ,其中, TitleView 就是標題欄,也就是我們常說的 TitleBar 或 ActionBar , ContentView 就是內容欄,它也是一個 FrameLayout ,主要用於承載我們的自定義根布局,即當我們調用 setContentView(...) 時,其實就是把我們自定義的布局設置到該 ContentView 中。

當 Activity 啟動完成後,最終就會渲染出上述層次結構的視圖。

因此,如果我們要求取得到子View 的 MeasureSpec ,那麼第一步就是求取得到頂層View(即 DecorView )的 MeasureSpec 。大致過程如下所示:

經過上述步驟求取得到 View 的 MeasureSpec 後,接下來就可以真正對 View 進行測量,求取 View 的最終測量寬/高:

Android 內部對視圖進行測量的過程是由 View#measure(int, int) 方法負責的,但是對於 View 和 ViewGroup ,其具體測量過程有所差異。

因此,對於測量過程,我們分別對 View 和 ViewGroup 進行分析:

綜上,無論是對 View 的測量還是 ViewGroup 的測量,都是由 View#measure(int widthMeasureSpec, int heightMeasureSpec) 方法負責,然後真正執行 View 測量的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法。

具體來說, View 直接在 onMeasure(...) 中測量並設置自己的最終測量寬/高。在默認測量情況下, View 的測量寬/高由其父容器的 MeasureSpec 和自身的 LayoutParams 共同決定,當 View 自身的測量模式為 LayoutParams.UNSPECIFIED 時,其測量寬/高為 android:minWidth / android:minHeight 和其背景寬/高之間的較大值,其餘情況皆為自身 MeasureSpec 指定的測量尺寸。

而對於 ViewGroup 來說,由於布局特性的豐富性,只能自己手動覆寫 onMeasure(...) 方法,實現自定義測量過程,但是總的思想都是先測量 子View 大小,最終才能確定自己的測量大小。

當確定了 View 的測量大小後,接下來就可以來確定 View 的布局位置了,也即將 View 放置到屏幕具體哪個位置。

View 的布局過程由 View#layout(...) 負責,其源碼如下:

View#layout(...) 主要就做了兩件事:

ViewGroup 的布局流程由 ViewGroup#layout(...) 負責,其源碼如下:

可以看到, ViewGroup#layout(...) 最終也是通過 View#layout(...) 完成自身的布局過程,一個注意的點是, ViewGroup#layout(...) 是一個 final 方法,因此子類無法覆寫該方法,主要是 ViewGroup#layout(...) 方法內部對子視圖動畫效果進行了相關設置。

由於 ViewGroup#layout(...) 內部最終調用的還是 View#layout(...) ,因此, ViewGroup#onLayout(...) 就會得到回調,用於處理 子View 的布局放置,其源碼如下:

由於不同的 ViewGroup ,其布局特性不同,因此 ViewGroup#onLayout(...) 是一個抽象方法,交由 ViewGroup 子類依據自己的布局特性,擺放其 子View 的位置。

當 View 的測量大小,布局位置都確定後,就可以最終將該 View 繪制到屏幕上了。

View 的繪制過程由 View#draw(...) 方法負責,其源碼如下:

其實注釋已經寫的很清楚了, View#draw(...) 主要做了以下 6 件事:

我們知道,在 Activity 啟動過程中,會調用到 ActivityThread.handleResumeActivity(...) ,該方法就是 View 視圖繪制的起始之處:

可以看到, ActivityThread.handleResumeActivity(...) 主要就是獲取到當前 Activity 綁定的 ViewManager ,最後調用 ViewManager.addView(...) 方法將 DecorView 設置到 PhoneWindow 上,也即設置到當前 Activity 上。 ViewManager 是一個介面, WindowManager 繼承 ViewManager ,而 WindowManagerImpl 實現了介面 WindowManager ,此處的 ViewManager.addView(...) 實際上調用的是 WindowManagerImpl.addView(...) ,源碼如下所示:

WindowManagerImpl.addView(...) 內部轉發到 WindowManagerGlobal.addView(...) :

在 WindowManagerGlobal.addView(...) 內部,會創建一個 ViewRootImpl 實例,然後調用 ViewRootImpl.setView(...) 將 ViewRootImpl 與 DecorView 關聯到一起:

ViewRootImpl.setView(...) 內部首先關聯了傳遞過來的 DecorView (通過屬性 mView 指向 DecorView 即可建立關聯),然後最終調用 requestLayout() ,而 requestLayout() 內部又會調用方法 scheleTraversals() :

ViewRootImpl.scheleTraversals() 內部主要做了兩件事:

Choreographer.postCallback(...) 會申請一次 VSYNC 中斷信號,當 VSYNC 信號到達時,便會回調 Choreographer.doFrame(...) 方法,內部會觸發已經添加的回調任務, Choreographer 的回調任務有以下四種類型:

因此, ViewRootImpl.scheleTraversals(...) 內部通過 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 發送的非同步視圖渲染消息就會得到回調,即回調 mTra

㈣ Android UI繪制之View繪制的工作原理

這是AndroidUI繪制流程分析的第二篇文章,主要分析界面中View是如何繪制到界面上的具體過程。

ViewRoot 對應於 ViewRootImpl 類,它是連接 WindowManager 和 DecorView 的紐帶,View的三大流程均是通過 ViewRoot 來完成的。在 ActivityThread 中,當 Activity 對象被創建完畢後,會將 DecorView 添加到 Window 中,同時會創建 ViewRootImpl 對象,並將 ViewRootImpl 對象和 DecorView 建立關聯。

measure 過程決定了 View 的寬/高, Measure 完成以後,可以通過 getMeasuredWidth 和 getMeasuredHeight 方法來獲取 View 測量後的寬/高,在幾乎所有的情況下,它等同於View的最終的寬/高,但是特殊情況除外。 Layout 過程決定了 View 的四個頂點的坐標和實際的寬/高,完成以後,可以通過 getTop、getBottom、getLeft 和 getRight 來拿到View的四個頂點的位置,可以通過 getWidth 和 getHeight 方法拿到View的最終寬/高。 Draw 過程決定了 View 的顯示,只有 draw 方法完成後 View 的內容才能呈現在屏幕上。

DecorView 作為頂級 View ,一般情況下,它內部會包含一個豎直方向的 LinearLayout ,在這個 LinearLayout 裡面有上下兩個部分,上面是標題欄,下面是內容欄。在Activity中,我們通過 setContentView 所設置的布局文件其實就是被加到內容欄中的,而內容欄id為 content 。可以通過下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通過 content.getChildAt(0) 可以得到設置的 view 。 DecorView 其實是一個 FrameLayout , View 層的事件都先經過 DecorView ,然後才傳遞給我們的 View 。

MeasureSpec 代表一個32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指測量模式,而 SpecSize 是指在某種測量模式下的規格大小。
SpecMode 有三類,如下所示:

UNSPECIFIED

EXACTLY

AT_MOST

LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。

對於頂級View,即DecorView和普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同確定;

對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同決定;

MeasureSpec一旦確定,onMeasure就可以確定View的測量寬/高。

小結一下

當子 View 的寬高採用 wrap_content 時,不管父容器的模式是精確模式還是最大模式,子 View 的模式總是最大模式+父容器的剩餘空間。

View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即測量、布局、繪制。其中 measure 確定 View 的測量寬/高, layout 確定 view 的最終寬/高和四個頂點的位置,而 draw 則將 View 繪制在屏幕上。

measure 過程要分情況,如果只是一個原始的 view ,則通過 measure 方法就完成了其測量過程,如果是一個 ViewGroup ,除了完成自己的測量過程外,還會遍歷調用所有子元素的 measure 方法,各個子元素再遞歸去執行這個流程。

如果是一個原始的 View,那麼通過 measure 方法就完成了測量過程,在 measure 方法中會去調用 View 的 onMeasure 方法,View 類裡面定義了 onMeasure 方法的默認實現:

先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源碼:

可以看到, getMinimumWidth 方法獲取的是 Drawable 的原始寬度。如果存在原始寬度(即滿足 intrinsicWidth > 0),那麼直接返回原始寬度即可;如果不存在原始寬度(即不滿足 intrinsicWidth > 0),那麼就返回 0。

接著看最重要的 getDefaultSize 方法:

如果 specMode 為 MeasureSpec.UNSPECIFIED 即未指定模式,那麼返回由方法參數傳遞過來的尺寸作為 View 的測量寬度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精確模式,那麼返回從 measureSpec 中取出的 specSize 作為 View 測量後的寬度和高度。

看一下剛才的表格:

當 specMode 為 EXACTLY 或者 AT_MOST 時,View 的布局參數為 wrap_content 或者 match_parent 時,給 View 的 specSize 都是 parentSize 。這會比建議的最小寬高要大。這是不符合我們的預期的。因為我們給 View 設置 wrap_content 是希望View的大小剛好可以包裹它的內容。

因此:

如果是一個 ViewGroup,除了完成自己的 measure 過程以外,還會遍歷去調用所有子元素的 measure 方法,各個子元素再遞歸去執行 measure 過程。

ViewGroup 並沒有重寫 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 這幾個方法專門用於測量子元素。

如果是 View 的話,那麼在它的 layout 方法中就確定了自身的位置(具體來說是通過 setFrame 方法來設定 View 的四個頂點的位置,即初始化 mLeft , mRight , mTop , mBottom 這四個值), layout 過程就結束了。

如果是 ViewGroup 的話,那麼在它的 layout 方法中只是確定了 ViewGroup 自身的位置,要確定子元素的位置,就需要重寫 onLayout 方法;在 onLayout 方法中,會調用子元素的 layout 方法,子元素在它的 layout 方法中確定自己的位置,這樣一層一層地傳遞下去完成整個 View 樹的 layout 過程。

layout 方法的作用是確定 View 本身的位置,即設定 View 的四個頂點的位置,這樣就確定了 View 在父容器中的位置;
onLayout 方法的作用是父容器確定子元素的位置,這個方法在 View 中是空實現,因為 View 沒有子元素了,在 ViewGroup 中則進行抽象化,它的子類必須實現這個方法。

1.繪制背景( background.draw(canvas); );
2.繪制自己( onDraw );
3.繪制 children( dispatchDraw(canvas) );
4.繪制裝飾( onDrawScrollBars )。

dispatchDraw 方法的調用是在 onDraw 方法之後,也就是說,總是先繪制自己再繪制子 View 。

對於 View 類來說, dispatchDraw 方法是空實現的,對於 ViewGroup 類來說, dispatchDraw 方法是有具體實現的。

通過 dispatchDraw 來傳遞的。 dispatchDraw 會遍歷調用子元素的 draw 方法,如此 draw 事件就一層一層傳遞了下去。dispatchDraw 在 View 類中是空實現的,在 ViewGroup 類中是真正實現的。

如果一個 View 不需要繪制任何內容,那麼就設置這個標記為 true,系統會進行進一步的優化。

當創建的自定義控制項繼承於 ViewGroup 並且不具備繪制功能時,就可以開啟這個標記,便於系統進行後續的優化;當明確知道一個 ViewGroup 需要通過 onDraw 繪制內容時,需要關閉這個標記。

參考:《Android開發藝術探索》

㈤ 關於Android WebView的那些事

[TOC]

Webkit是一個開源瀏覽器項目,其中,對Android開發者來說,或多或少的都有些接觸。 在應用層來看,最經常使用無非這么幾個類:WebView(Android中最為復雜,也是最為簡單的一個View,繼承自AbsoluteLayout),WebViewClient、WebChromeClient(作為回調控制類)、WebSettings(進行設置項的配置)等;Webkit內部包含了網路請求、頁面渲染、Js引擎等等。在Android4.4之前的版本中,系統使用的是Webkit內核,其後,切換到Google的Chromium內核。本文主要介紹的是在Android中,如何使用Webkit進行H5頁面的展現,以及常見問題的分析手段。

下面的內容抄自網路 & 亂七八糟的地方,簡單了解一下。

<b><i>前面都是吹牛逼的信息,如何使用Webkit來更好的搬磚? 且聽如下分解</i></b>

XML布局中丟一個 <WebView> 標簽,然後再 Activity 或者 Fragment 中 findViewById ,進而 loadUrl ,一般也沒人這么簡單的用,除非寫Demo。很簡單,它就是一個Layout,提供了一個調用載入頁面的介面,不寫範例了,能看到這篇文章的都看過Google的API說明。

主要涉及到WebView和WebSettings兩個類。

例如:

其實就是WebView的父類ViewGroup和View的方法,不多說了。不過需要注意的是,不是所有的View或ViewGroup的方法對WebView都生效。

列舉幾類常用的,幾乎所有App的 WebView 都會設置的屬性:

</br>

如何處理頁面跳轉以及特殊 Scheme

這個回調可以說是最容易出問題的一個回調,表示什麼? 字面意思,讓你重寫這個URL 的loading,比如點擊html打電話的一個 <a href=「tel:110」> 標簽,作為一個有節操、有責任心的瀏覽器,你需要處理 H5常用的幾個Scheme :

除此之外,還有各個應用自定義的scheme ,舉個例子,支付寶的支付Scheme : alipay: 。 這里的返回值,就代表你有沒有能力處理這個url,沒有的話Webkit就默認處理了。
需要注意的是,這個回調的觸發的絕大多數情況是點擊頁面的 <a href="xxxx"> a標簽,在Android中 loadUrl("http://www..com") ,是不會回調的,為什麼不會回調,各位自行理解吧。

超鏈接 <a> 標簽怎麼寫: 點我
特別說下窗口常見的兩種打開方式:

針對單頁模式的WebView框架(所有的html窗口均使用同一個WebView實例),不需要關注target的。
如果作為一個成熟的瀏覽器框架的話,是需要支持Html、JavaScript使用新窗口打開頁面,需要實現如下回調:

還有一個相關設置項: WebSettings.
此時,系統將不會再回調 shouldOverrideUrlLoading 。新窗口邏輯的具體實現機制,可以參考系統browser實現邏輯。

<b> 這里有個坑 </b>
Android 4.4版本 ,如果實現了onCreateWindow,也就是說頁面 <a> 標簽是這么寫的: <a href="http://www..com" target="_blank"> ,點擊此鏈接打開的新WebView窗口,此窗口中的url點擊,是不會觸發 shouldOverrideUrlLoading 。 這是剛替換成Chrominum內核出的一個bug。本人並沒在新版本上驗證是否已經修復。

另外,根據不同的Rom,底層實現是不一樣的,有的ROM會幫你處理各種調起scheme,也就是startActivity,有的ROM點一個url,就會拋一個intent出來,讓用戶選擇系統瀏覽器進行載入。

系統默認,提供了一個介面:

有什麼安全隱患呢?
戳這里

如果不知道Js怎麼寫, 請戳我

用PC的截圖意思一下,看出區別了吧。 這里確定、取消點擊以後就得調用 JsResult、JsPromptResult 的 confirm或者cancel。

因為安全問題,大一些的App Native與Js通信都不再用 WebView.addJavascriptInterface(Object) 了,都改用JsPrompt,因為JsPrompt中有message、有JsPromptResult可以返回給Js一些信息,所以橋選中了JsPrompt,另一個備選方案是JsConsole。

大體有這么幾種方式進行傳遞

具體方案實現時,多方面考慮使用何種方式。

還有一個比較牛逼的

系統源碼中均有方法注釋,怎麼用自己看吧。
那麼問題來了

查了下,只有這兩個相關的:
WebBackForwardList BackForwardList()
void clearHistory()
系統提供的關於歷史記錄的操作並不多,因為,不支持單條刪除啊,啊啊啊!
WebViewClient中,還有一個相關callback,當系統更新歷史記錄時回調:
void doUpdateVisitedHistory(WebView view, String url, boolean isReload)

<b>相關問題分析法:歷史棧回退錯誤的定位</b>

絕大多數回退錯誤是由於介面調用、回調中邏輯執行時序錯誤。
定位方法:利用 BackForwardList , doUpdateVisitedHistory 兩個介面在 loadUrl、onPageStart、onPageFinish 以及邏輯相關的地方調用,打log,查看歷史棧,這里注意下由於loarl是非同步的,需要考慮是否加延遲等等保證調用時機的准確。
本人曾經遇到一個問題:在WebChromeClient中的 JsPrompt回調中,直接進行WebView.goBack操作,結果發現WebView確實回退到上一個頁面,但是BackFowardList當前頁面的index未更新的問題,具體見另一個篇blog。

網上有很多關於WebView內存泄露的討論,據傳,老版本的WebView在展示大量圖片的時候,即使 WebView.destory() WebView=null ,也不會銷毀。
在新版本上,實際測試結果:compileSDKVersion 23 不會泄露。
一般,我們如何銷毀WebView比較保險?

這個問題好大。。。
暫時不介紹,另起blog進行說明。

解決方案:
實現回調 void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)

首先,提幾個需要注意的點:

個人歸納總結幾點:

step1 進入開發者模式,勾選「顯示布局邊界」;
step 2,回到你想查看的界面; step 3 假如內容區只有一層基本就是H5 WebView的,多個層級,就是Native。

看到左右圖的差異了吧。
還有另一種方法,RD屌絲們看這里,特別說明,這種方法不太適合瀏覽器。 (自有內核,可能會不準確)

好了,就介紹到這里,零零散散的幾年前寫的文章,第一篇blog,如有不對的地方,還懇請大家指正。

閱讀全文

與androidview分析相關的資料

熱點內容
網站小說源碼 瀏覽:299
php用什麼ide 瀏覽:865
網上預約課程app哪個好 瀏覽:150
android兼容測試工具 瀏覽:96
雲伺服器不支持虛擬化怎麼辦 瀏覽:189
加密方式的演變 瀏覽:364
java常用演算法pdf 瀏覽:734
伺服器數據遇到異常什麼原因 瀏覽:450
phpexif信息 瀏覽:542
單片機三位元組浮點數 瀏覽:756
命令與征服泰伯利亞戰爭下載 瀏覽:378
c窗口界面編程 瀏覽:23
hypermill編程能做模板嗎 瀏覽:782
計算機網路最經典的演算法 瀏覽:628
華為思科的配置命令 瀏覽:869
linux設置光碟啟動 瀏覽:361
程序員俱樂部注冊 瀏覽:19
洗潔精雞蛋盤解壓視頻 瀏覽:295
企業內網加密app 瀏覽:51
騰訊雲伺服器怎麼設置本地網路 瀏覽:625