安卓Webview網(wǎng)頁(yè)秒開(kāi)策略探索

痛點(diǎn)是什么养葵?

網(wǎng)頁(yè)加載緩慢,白屏邦尊,使用卡頓背桐。

為何有這種問(wèn)題?

1.調(diào)用loadUrl()方法的時(shí)候蝉揍,才會(huì)開(kāi)始網(wǎng)頁(yè)加載流程
2.js臃腫問(wèn)題
3.加載圖片太多
4.webview本身問(wèn)題

webiew是怎么加載網(wǎng)頁(yè)的呢链峭?

webview初始化->DOM下載→DOM解析→CSS請(qǐng)求+下載→CSS解析→渲染→繪制→合成

優(yōu)化方向是?

1.webview本身優(yōu)化

  • 提前內(nèi)核初始化
    代碼:
public class App extends Application {

    private WebView mWebView ;
    @Override
    public void onCreate() {
        super.onCreate();
        mWebView = new WebView(new MutableContextWrapper(this));
    }
}

效果:初次內(nèi)核初始化大概2000ms又沾,第二次50ms以內(nèi)

  • webview復(fù)用池
    代碼:
public class WebPools {


    private final Queue<WebView> mWebViews;

    private Object lock = new Object();
    private static WebPools mWebPools = null;

    private static final AtomicReference<WebPools> mAtomicReference = new AtomicReference<>();
    private static final String TAG=WebPools.class.getSimpleName();

    private WebPools() {
        mWebViews = new LinkedBlockingQueue<>();
    }
    public static WebPools getInstance() {

        for (; ; ) {
            if (mWebPools != null)
                return mWebPools;
            if (mAtomicReference.compareAndSet(null, new WebPools()))
                return mWebPools=mAtomicReference.get();

        }
    }
    public void recycle(WebView webView) {
        recycleInternal(webView);
    }
    public WebView acquireWebView(Activity activity) {
        return acquireWebViewInternal(activity);
    }
    private WebView acquireWebViewInternal(Activity activity) {
        WebView mWebView = mWebViews.poll();
        LogUtils.i(TAG,"acquireWebViewInternal  webview:"+mWebView);
        if (mWebView == null) {
            synchronized (lock) {
                return new WebView(new MutableContextWrapper(activity));
            }
        } else {
            MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
            mMutableContextWrapper.setBaseContext(activity);
            return mWebView;
        }
    }
    private void recycleInternal(WebView webView) {
        try {
            if (webView.getContext() instanceof MutableContextWrapper) {
                MutableContextWrapper mContext = (MutableContextWrapper) webView.getContext();
             mContext.setBaseContext(mContext.getApplicationContext());
                LogUtils.i(TAG,"enqueue  webview:"+webView);
                mWebViews.offer(webView);
            }
            if(webView.getContext() instanceof  Activity){
//            throw new RuntimeException("leaked");
                LogUtils.i(TAG,"Abandon this webview  弊仪, It will cause leak if enqueue !");
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

帶來(lái)的問(wèn)題:內(nèi)存泄漏

  • 獨(dú)立進(jìn)程,進(jìn)程預(yù)加載
    代碼:
        <service
            android:name=".PreWebService"
            android:process=":web"/>
        <activity
            android:name=".WebActivity"
            android:process=":web"/>

啟動(dòng)webview頁(yè)面前杖刷,先啟動(dòng)PreWebService把[web]進(jìn)程創(chuàng)建了励饵,當(dāng)啟動(dòng)WebActivity時(shí),系統(tǒng)發(fā)發(fā)現(xiàn)[web]進(jìn)程已經(jīng)存在了滑燃,就不需要花費(fèi)時(shí)間Fork出新的[web]進(jìn)程了役听。

  • 使用x5內(nèi)核
    直接使用騰訊的x5內(nèi)核,替換原生的瀏覽器內(nèi)核

  • 其他的解決方案:
    1.設(shè)置webview緩存
    2.加載動(dòng)畫(huà)/最后讓圖片下載
    3.渲染時(shí)關(guān)掉圖片加載
    4.設(shè)置超時(shí)時(shí)間
    5.開(kāi)啟軟硬件加速

2.加載資源時(shí)的優(yōu)化
這種優(yōu)化多使用第三方表窘,下面有介紹

3.網(wǎng)頁(yè)端的優(yōu)化
由網(wǎng)頁(yè)的前端工程師優(yōu)化網(wǎng)頁(yè)典予,或者說(shuō)是和移動(dòng)端一起,將網(wǎng)頁(yè)實(shí)現(xiàn)增量更新乐严,動(dòng)態(tài)更新瘤袖。app內(nèi)置css,js文件并控制版本

注意:如果你寄希望于只通過(guò)webview的setting來(lái)加速網(wǎng)頁(yè)的加載速度,那你就要失望了麦备。只修改設(shè)置孽椰,能做的提升非常少。所以本文就著重分析比較下凛篙,現(xiàn)在可以使用的第三方webview框架的優(yōu)缺點(diǎn)黍匾。

現(xiàn)在大廠的方法有以下幾種:

  1. VasSonic
  2. TBS騰訊瀏覽服務(wù)
  3. 百度app方案
  4. 今日頭條方案

VasSonic

參考文章:
https://blog.csdn.net/tencent__open/article/details/77324952
接入方法:
STEP1:

    //導(dǎo)入 Tencent/VasSonic
    implementation 'com.tencent.sonic:sdk:3.1.0'

STEP2:

//創(chuàng)建一個(gè)類(lèi)繼承SonicRuntime
//SonicRuntime類(lèi)主要提供sonic運(yùn)行時(shí)環(huán)境,包括Context呛梆、用戶UA锐涯、ID(用戶唯一標(biāo)識(shí),存放數(shù)據(jù)時(shí)唯一標(biāo)識(shí)對(duì)應(yīng)用戶)等等信息填物。以下代碼展示了SonicRuntime的幾個(gè)方法纹腌。
public class TTPRuntime extends SonicRuntime
{
    //初始化
    public TTPRuntime( Context context )
    {
        super(context);
    }
    
    @Override
    public void log(
            String tag ,
            int level ,
            String message )
    {
        //log設(shè)置
    }
    
    //獲取cookie
    @Override
    public String getCookie( String url )
    {
        return null;
    }
    
    //設(shè)置cookid
    @Override
    public boolean setCookie(
            String url ,
            List<String> cookies )
    {
        return false;
    }
    
    //獲取用戶UA信息
    @Override
    public String getUserAgent()
    {
        return "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36";
    }
    
    //獲取用戶ID信息
    @Override
    public String getCurrentUserAccount()
    {
        return "ttpp";
    }
    
    //是否使用Sonic加速
    @Override
    public boolean isSonicUrl( String url )
    {
        return true;
    }
    
    //創(chuàng)建web資源請(qǐng)求
    @Override
    public Object createWebResourceResponse(
            String mimeType ,
            String encoding ,
            InputStream data ,
            Map<String, String> headers )
    {
        return null;
    }
    
    //網(wǎng)絡(luò)屬否允許
    @Override
    public boolean isNetworkValid()
    {
        return true;
    }
    
    @Override
    public void showToast(
            CharSequence text ,
            int duration )
    { }
    
    @Override
    public void postTaskToThread(
            Runnable task ,
            long delayMillis )
    { }
    
    @Override
    public void notifyError(
            SonicSessionClient client ,
            String url ,
            int errorCode )
    { }
    
    //設(shè)置Sonic緩存地址
    @Override
    public File getSonicCacheDir()
    {
        return super.getSonicCacheDir();
    }
}

STEP3:

//創(chuàng)建一個(gè)類(lèi)繼承SonicSessionClien
//SonicSessionClient主要負(fù)責(zé)跟webView的通信,比如調(diào)用webView的loadUrl滞磺、loadDataWithBaseUrl等方法升薯。
public class WebSessionClientImpl extends SonicSessionClient
{
    private WebView webView;
    
    //綁定webview
    public void bindWebView(WebView webView) {
        this.webView = webView;
    }
    
    //加載網(wǎng)頁(yè)
    @Override
    public void loadUrl(String url, Bundle extraData) {
        webView.loadUrl(url);
    }
    
    //加載網(wǎng)頁(yè)
    @Override
    public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding,
            String historyUrl) {
        webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
    }
    
    //加載網(wǎng)頁(yè)
    @Override
    public void loadDataWithBaseUrlAndHeader(
            String baseUrl ,
            String data ,
            String mimeType ,
            String encoding ,
            String historyUrl ,
            HashMap<String, String> headers )
    {
        if( headers.isEmpty() )
        {
            webView.loadDataWithBaseURL( baseUrl, data, mimeType, encoding, historyUrl );
        }
        else
        {
            webView.loadUrl( baseUrl,headers );
        }
    }
}

STEP4:

//創(chuàng)建activity
public class WebActivity extends AppCompatActivity
{
    private String url = "http://www.baidu.com";
    private SonicSession sonicSession;
    
    @Override
    protected void onCreate( @Nullable Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_web);
        initView();
    }
    
    private void initView()
    {
        getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        
        //初始化 可放在Activity或者Application的onCreate方法中
        if( !SonicEngine.isGetInstanceAllowed() )
        {
            SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
        }
        //設(shè)置預(yù)加載
        SonicSessionConfig config = new SonicSessionConfig.Builder().build();
        SonicEngine.getInstance().preCreateSession( url,config );
        
        WebSessionClientImpl client = null;
        //SonicSessionConfig  設(shè)置超時(shí)時(shí)間、緩存大小等相關(guān)參數(shù)击困。
        //創(chuàng)建一個(gè)SonicSession對(duì)象涎劈,同時(shí)為session綁定client广凸。session創(chuàng)建之后sonic就會(huì)異步加載數(shù)據(jù)了
        sonicSession = SonicEngine.getInstance().createSession( url,config );
        if( null!= sonicSession )
        {
            sonicSession.bindClient( client = new WebSessionClientImpl() );
        }
        //獲取webview
        WebView webView = (WebView)findViewById( R.id.webview_act );
        webView.setWebViewClient( new WebViewClient()
        {
            @Override
            public void onPageFinished(
                    WebView view ,
                    String url )
            {
                super.onPageFinished( view , url );
                if( sonicSession != null )
                {
                    sonicSession.getSessionClient().pageFinish( url );
                }
            }
            
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(
                    WebView view ,
                    WebResourceRequest request )
            {
                return shouldInterceptRequest( view, request.getUrl().toString() );
            }
            //為clinet綁定webview,在webView準(zhǔn)備發(fā)起loadUrl的時(shí)候通過(guò)SonicSession的onClientReady方法通知sonicSession: webView ready可以開(kāi)始loadUrl了蛛枚。這時(shí)sonic內(nèi)部就會(huì)根據(jù)本地的數(shù)據(jù)情況執(zhí)行webView相應(yīng)的邏輯(執(zhí)行l(wèi)oadUrl或者loadData等)
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(
                    WebView view ,
                    String url )
            {
                if( sonicSession != null )
                {
                    return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
                }
                return null;
            }
        });
        //webview設(shè)置
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.removeJavascriptInterface("searchBoxJavaBridge_");
        //webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");
        webSettings.setAllowContentAccess(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setAppCacheEnabled(true);
        webSettings.setSavePassword(false);
        webSettings.setSaveFormData(false);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);

        //為clinet綁定webview谅海,在webView準(zhǔn)備發(fā)起loadUrl的時(shí)候通過(guò)SonicSession的onClientReady方法通知sonicSession: webView ready可以開(kāi)始loadUrl了。這時(shí)sonic內(nèi)部就會(huì)根據(jù)本地的數(shù)據(jù)情況執(zhí)行webView相應(yīng)的邏輯(執(zhí)行l(wèi)oadUrl或者loadData等)蹦浦。
        if( client != null )
        {
            client.bindWebView( webView );
            client.clientReady();
        }
        else
        {
            webView.loadUrl( url );
        }
    }
    
    @Override
    public void onBackPressed()
    {
        super.onBackPressed();
    }
    
    @Override
    protected void onDestroy()
    {
        if( null != sonicSession )
        {
            sonicSession.destroy();
            sonicSession = null;
        }
        super.onDestroy();
    }
}

簡(jiǎn)單分析下它的核心思想:
并行扭吁,充分利用webview初始化的時(shí)間進(jìn)行一些數(shù)據(jù)的處理。在包含webview的activity啟動(dòng)時(shí)會(huì)一邊進(jìn)行webview的初始化邏輯盲镶,一邊并行的執(zhí)行sonic的邏輯侥袜。這個(gè)sonic邏輯就是網(wǎng)頁(yè)的預(yù)加載
原理:

  • Quick模式
    模式分類(lèi):
    1.無(wú)緩存模式
    流程:


    sonicQuickModeFirst.png

左邊的webview流程:webview初始化后調(diào)用SonicSession的onClientReady方法,告知
webview已經(jīng)初始化完畢徒河。

client.clientReady();

右邊的sonic流程:
1.創(chuàng)建SonicEngine對(duì)象
2.通過(guò)SonicCacheInterceptor獲取本地緩存的url數(shù)據(jù)
3.數(shù)據(jù)為空就發(fā)送一個(gè)CLIENT_CORE_MSG_PRE_LOAD的消息到主線程
4.通過(guò)SonicSessionConnection建立一個(gè)URLConnection
5.連接獲取服務(wù)器返回的數(shù)據(jù)系馆,并在讀取網(wǎng)絡(luò)數(shù)據(jù)的時(shí)候不斷判斷webview是否發(fā)起資源攔截請(qǐng)求。如果發(fā)了顽照,就中斷網(wǎng)絡(luò)數(shù)據(jù)的讀取,把已經(jīng)讀取的和未讀取的數(shù)據(jù)拼接成橋接流SonicSessionStream并賦值給SonicSession的pendingWebResourceStream闽寡,如果網(wǎng)絡(luò)讀取完成后webview還沒(méi)有初始化完成代兵,就會(huì)cancel掉CLIENT_CORE_MSG_PRE_LOAD消息,同時(shí)發(fā)送CLIENT_CORE_MSG_FIRST_LOAD消息
6.之后再對(duì)html內(nèi)容進(jìn)行模版分割及數(shù)據(jù)保存
7.如果webview處理了CLIENT_CORE_MSG_PRE_LOAD這個(gè)消息爷狈,它就會(huì)調(diào)用webview的loadUrl,之后webview會(huì)調(diào)用自身的資源攔截方法植影,在這個(gè)方法中,會(huì)將之前保存的pendingWebResourceStream返回給webview讓其解析渲染涎永,
8.如果webview處理的是CLIENT_CORE_MSG_FIRST_LOAD消息思币,webview如果沒(méi)有l(wèi)oadUrl過(guò)就會(huì)調(diào)用loadDataWithBaseUrl方法加載之前讀取的網(wǎng)絡(luò)數(shù)據(jù),這樣webview就可以直接做解析渲染了羡微。
2.有緩存模式
完全緩存流程:
左邊webview的流程跟無(wú)緩存一致谷饿,右邊sonic的流程會(huì)通過(guò)SonicCacheInterceptor獲取本地?cái)?shù)據(jù)是否為空,不為空就會(huì)發(fā)生CLIENT_CORE_MSG_PRE_LOAD消息妈倔,之后webview就會(huì)使用loadDataWithBaseUrl加載網(wǎng)頁(yè)進(jìn)行渲染了


TBS騰訊瀏覽服務(wù)

官網(wǎng)
集成方法博投,請(qǐng)按照官網(wǎng)的來(lái)操作即可。這里直接放上使用后的效果圖吧


百度app方案

來(lái)看下百度app對(duì)webview處理的方案

  1. 后端直出
    后端直出-頁(yè)面靜態(tài)直出
    后端服務(wù)器獲取html所有首屏內(nèi)容盯蝴,包含首屏展現(xiàn)所需的內(nèi)容和樣式毅哗。這樣客戶端獲取整個(gè)網(wǎng)頁(yè)并加載時(shí),內(nèi)核可以直接進(jìn)行渲染捧挺。
    這里服務(wù)端要提供一個(gè)接口給客戶端取獲取網(wǎng)頁(yè)的全部?jī)?nèi)容虑绵。而且
    獲取的網(wǎng)頁(yè)中一些需要使用客戶端的變量的使用宏替換,在客戶端加載網(wǎng)頁(yè)的時(shí)候替換成特定的內(nèi)容闽烙,已適應(yīng)不同用戶的設(shè)置翅睛,例如字體大小、頁(yè)面顏色等等。
    但是這個(gè)方案還有些問(wèn)題就是網(wǎng)絡(luò)圖片沒(méi)有處理宏所,還是要花費(fèi)時(shí)間起獲取圖片酥艳。

2.智能預(yù)取-提前化網(wǎng)絡(luò)請(qǐng)求
提前從網(wǎng)絡(luò)中獲取部分落地頁(yè)html,緩存到本地爬骤,當(dāng)用戶點(diǎn)擊查看時(shí)充石,只需要從緩存中加載即可。

3.通用攔截-緩存共享霞玄、請(qǐng)求并行
直出解決了文字展現(xiàn)的速度問(wèn)題骤铃,但是圖片加載渲染速度還不理想。
借由內(nèi)核的shouldInterceptRequest回調(diào)坷剧,攔截落地頁(yè)圖片請(qǐng)求惰爬,由客戶端調(diào)用圖片下載框架進(jìn)行下載,并以管道方式填充到內(nèi)核的WebResourceResponse中惫企。就是說(shuō)在shouldInterceptRequest攔截所有URL撕瞧,之后只針對(duì)后綴是.PNG/.JPG等圖片資源,使用第三方圖片下載工具類(lèi)似于Fresco進(jìn)行下載并返回一個(gè)InputStream狞尔。

總結(jié):

  • 提前做:包括預(yù)創(chuàng)建WebView和預(yù)取數(shù)據(jù)
  • 并行做:包括圖片直出&攔截加載丛版,框架初始化階段開(kāi)啟異步線程準(zhǔn)備數(shù)據(jù)等
  • 輕量化:對(duì)于前端來(lái)說(shuō),要盡量減少頁(yè)面大小偏序,刪減不必要的JS和CSS页畦,不僅可以縮短網(wǎng)絡(luò)請(qǐng)求時(shí)間,還能提升內(nèi)核解析時(shí)間
  • 簡(jiǎn)單化:對(duì)于簡(jiǎn)單的信息展示頁(yè)面研儒,對(duì)內(nèi)容動(dòng)態(tài)性要求不高的場(chǎng)景豫缨,可以考慮使用直出替代hybrid,展示內(nèi)容直接可渲染端朵,無(wú)需JS異步加載

今日頭條方案

那今日頭條是怎么處理的呢好芭?
1.assets文件夾內(nèi)預(yù)置了文章詳情頁(yè)面的css/js等文件,并且能進(jìn)行版本控制
2.webview預(yù)創(chuàng)建的同時(shí)逸月,預(yù)先加載一個(gè)使用JAVA代碼拼接的html栓撞,提前對(duì)js/css資源進(jìn)行解析。
3.文章詳情頁(yè)面使用預(yù)創(chuàng)建的webview碗硬,這個(gè)webview已經(jīng)預(yù)加載了html瓤湘,之后就調(diào)用js來(lái)設(shè)置頁(yè)面內(nèi)容
3.對(duì)于圖片資源,使用ContentProvider來(lái)獲取恩尾,而圖片則是使用Fresco來(lái)下載的

content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65cinMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965/3

整理下這幾個(gè)大廠的思路
目的:網(wǎng)頁(yè)秒開(kāi)
策略:

  • 針對(duì)客戶端
    1.預(yù)創(chuàng)建(application onCreate 時(shí))webview
    1.1預(yù)創(chuàng)建的同時(shí)加載帶有css/js的html文本
    2.webview復(fù)用池
    3.webview setting的設(shè)置
    4.預(yù)取網(wǎng)頁(yè)并緩存弛说,預(yù)先獲取html并緩存本地,需要是從緩存中加載即可
    5.資源攔截并行加載翰意,內(nèi)核初始化和資源加載同時(shí)進(jìn)行木人。
  • 針對(duì)服務(wù)端
    1.直出網(wǎng)頁(yè)的拼裝信柿,服務(wù)端時(shí)獲取網(wǎng)頁(yè)的全部?jī)?nèi)容,客戶端獲取后直接加載
    2.客戶端本地html資源的版本控制
  • 針對(duì)網(wǎng)頁(yè)前端
    1.刪減不必要的js/css
    2.配合客戶端使用VasSonic醒第,只對(duì)特定的內(nèi)容進(jìn)行頁(yè)面更新與下載渔嚷。

自己的想法:
1.網(wǎng)頁(yè)秒開(kāi)的這個(gè)需求,如果如果只是客戶端來(lái)做稠曼,感覺(jué)只是做了一半形病,最好還是前后端一起努力來(lái)優(yōu)化。
2.但是只做客戶端方面的優(yōu)化也是可以的霞幅,筆者實(shí)際測(cè)試了下漠吻,通過(guò)預(yù)取的方式,的確能做到秒開(kāi)網(wǎng)頁(yè)司恳。
3.今年就上5G了途乃,有可能在5G的網(wǎng)絡(luò)下,網(wǎng)頁(yè)加載根本就不是問(wèn)題了呢扔傅。


小技巧

修復(fù)白屏現(xiàn)象:系統(tǒng)處理view繪制的時(shí)候耍共,有一個(gè)屬性setDrawDuringWindowsAnimating,這個(gè)屬性是用來(lái)控制window做動(dòng)畫(huà)的過(guò)程中是否可以正常繪制猎塞,而恰好在Android 4.2到Android N之間划提,系統(tǒng)為了組件切換的流程性考慮,該字段為false邢享,我們可以利用反射的方式去手動(dòng)修改這個(gè)屬性

/**
     * 讓 activity transition 動(dòng)畫(huà)過(guò)程中可以正常渲染頁(yè)面
     */
    private void setDrawDuringWindowsAnimating(View view) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 1 android n以上  & android 4.1以下不存在此問(wèn)題,無(wú)須處理
            return;
        }
        // 4.2不存在setDrawDuringWindowsAnimating淡诗,需要特殊處理
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return;
        }
        try {
            // 4.3及以上骇塘,反射setDrawDuringWindowsAnimating來(lái)實(shí)現(xiàn)動(dòng)畫(huà)過(guò)程中渲染
            ViewParent rootParent = view.getRootView().getParent();
            Method method = rootParent.getClass()
                    .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * android4.2可以反射handleDispatchDoneAnimating來(lái)解決
     */
    private void handleDispatchDoneAnimating(View paramView) {
        try {
            ViewParent localViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace();
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市韩容,隨后出現(xiàn)的幾起案子款违,更是在濱河造成了極大的恐慌,老刑警劉巖群凶,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件插爹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡请梢,警方通過(guò)查閱死者的電腦和手機(jī)赠尾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)毅弧,“玉大人气嫁,你說(shuō)我怎么就攤上這事」蛔” “怎么了寸宵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵崖面,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我梯影,道長(zhǎng)巫员,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任甲棍,我火速辦了婚禮简识,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘救军。我一直安慰自己财异,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布唱遭。 她就那樣靜靜地躺著戳寸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拷泽。 梳的紋絲不亂的頭發(fā)上疫鹊,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音司致,去河邊找鬼拆吆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脂矫,可吹牛的內(nèi)容都是我干的枣耀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼庭再,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼捞奕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拄轻,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤颅围,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后恨搓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體院促,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年斧抱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了常拓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夺姑,死狀恐怖墩邀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盏浙,我是刑警寧澤眉睹,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布荔茬,位于F島的核電站,受9級(jí)特大地震影響竹海,放射性物質(zhì)發(fā)生泄漏慕蔚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一斋配、第九天 我趴在偏房一處隱蔽的房頂上張望孔飒。 院中可真熱鬧,春花似錦艰争、人聲如沸坏瞄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸠匀。三九已至,卻和暖如春逾柿,著一層夾襖步出監(jiān)牢的瞬間缀棍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工机错, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爬范,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓弱匪,卻偏偏與公主長(zhǎng)得像青瀑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萧诫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容