• android原生browser分析(一)--Application


    类Browser.java是整个应用的Application.其代码例如以下:

     
    public class Browser extends Application {
     
        @Override
        public void onCreate() {
            super.onCreate();
     
            // create CookieSyncManager with current Context
            CookieSyncManager.createInstance(this);
            BrowserSettings.initialize(getApplicationContext());
            Preloader.initialize(getApplicationContext());
        }
     
    }

    在Browser的创建方法onCreate方法中进行了三个操作。

    1、创建了一个CookieSyncManager单例对象。CookieSyncManager用于管理存储在本地数据库的cookies。

    2、初始化BrowserSettings。BrowserSettings是浏览器配置的管理单例类。

    3、初始化Preloader。Preloader是处理预载入请求的单例类。


    关于CookieSyncManager 

    我们来看看CookieSyncManager类及createInstance方法,

    public final class CookieSyncManager extends WebSyncManager {
     
        private static CookieSyncManager sRef;
     
        private CookieSyncManager(Context context) {
            	  super(context, "CookieSyncManager");
    }
    //获取CookieSyncManager 对象
    public static synchronized CookieSyncManager getInstance() {
            return sRef;
    }
    //创建CookieSyncManager 对象
        public static synchronized CookieSyncManager createInstance(
                Context context) {
            if (sRef == null) {
                sRef = new CookieSyncManager(context);
            }
            return sRef;
    }
     
    protected void syncFromRamToFlash() {
            CookieManager manager = CookieManager.getInstance();
            if (!manager.acceptCookie()) {
                return;
            }
            manager.flushCookieStore();
        }
    }
    CookieSyncManager是一个final类,这里用到了单例模式。没什么好讲的。CookieSyncManager继承自WebSyncManager 类,syncFromRamToFlash是个什么方法,后面将会介绍。再来看看WebSyncManager类。
    abstract class WebSyncManager implements Runnable {
        // 同步消息的消息码
        private static final int SYNC_MESSAGE = 101;
    // 以毫秒为单位的同步消息(即时)的时延
    private static int SYNC_NOW_INTERVAL = 100; // 100 毫秒
    // 以毫秒为单位的同步消息(稍后)的时延
    private static int SYNC_LATER_INTERVAL = 5 * 60 * 1000; // 5分钟
    // 同步线程
       	private Thread mSyncThread;
        // 线程名
       	private String mThreadName;
        // 同步线程的处理Handler
        protected Handler mHandler;
        // 持久存储的数据库
        protected WebViewDatabase mDataBase;
    // 调用開始同步和停止同步的參考次数
    private int mStartSyncRefCount;
     
        private class SyncHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == SYNC_MESSAGE) {
                    syncFromRamToFlash();
                    // 发送延时消息来请求稍后的同步,时间间隔5分钟
                    Message newmsg = obtainMessage(SYNC_MESSAGE);
                    sendMessageDelayed(newmsg, SYNC_LATER_INTERVAL);
                }
            }
        }
     
        protected WebSyncManager(Context context, String name) {
            mThreadName = name;
            if (context != null) {
                mDataBase = WebViewDatabase.getInstance(context);
                mSyncThread = new Thread(this);
                mSyncThread.setName(mThreadName);
                mSyncThread.start();
            } else {
                //exception
            }
        }
     
        protected Object clone() throws CloneNotSupportedException {
            //throw exception
        }
     
        public void run() {    
            Looper.prepare(); // 为同步handler准备Looper对象
            mHandler = new SyncHandler();
            onSyncInit();
            // 在onSyncInit() 完毕之后减少优先级
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
     
            Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
            mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
     
            Looper.loop();
        }
     
        public void sync() {
            //...
            mHandler.removeMessages(SYNC_MESSAGE);
            Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
            mHandler.sendMessageDelayed(msg, SYNC_NOW_INTERVAL);
        }
     
        public void resetSync() {
            //...
            mHandler.removeMessages(SYNC_MESSAGE);
            Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
            mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
        }
     
        public void startSync() {
            //...
            if (++mStartSyncRefCount == 1) {
                Message msg = mHandler.obtainMessage(SYNC_MESSAGE);
                mHandler.sendMessageDelayed(msg, SYNC_LATER_INTERVAL);
            }
        }
     
        public void stopSync() {
            //...
            if (--mStartSyncRefCount == 0) {
                mHandler.removeMessages(SYNC_MESSAGE);
            }
        }
     
        protected void onSyncInit() {
        }
     
        abstract void syncFromRamToFlash();
    }
     
    由此可见。WebSyncManager是实现了Runnable 接口的抽象类,当中的抽象方法就是上面提到的syncFromRamToFlash法,CookieSyncManagerWebSyncManager 的实现类并覆写了该方法。WebSyncManager中默认的同步时间间隔是5分钟,也就是Cookies的同步周期,WebSyncManager在创建的时候就会启动自身的线程。并依照同步周期来对cookies做同步。同步的详细实现即是syncFromRamToFlash()方法,WebSyncManager类中有下面几个重要的方法:

    resetSync() 又一次同步,即清除消息队列的同步消息。又一次发送延时5分钟的延时同步消息。

    startSync() 開始同步,即參考次数为0时,发送延时5分钟的延时同步消息,次数加1.

    stopSync() 停止同步。即清除消息队列的同步消息

    sync() 马上同步。即清除消息队列的同步消息,又一次发送延时100毫秒的延时同步消息。

    我们来看看CookieSyncManager中覆写WebSyncManager 中的syncFromRamToFlash方法,这种方法也就是cookies同步的方法。同步数据从RAMFLASH

    protected void syncFromRamToFlash() {
            CookieManager manager = CookieManager.getInstance();
            if (!manager.acceptCookie()) {
                return;
            }
            manager.flushCookieStore();
        }

    进行了三步操作:

    1、通过getInstance()获取CookieManager 对象。

    2、推断CookieManager 对象能否够接受cookies。不能则返回。

    3、调用CookieManager 对象的flushCookieStore()方法来实现同步。

    先来看看getInstance()方法。

    public static synchronized CookieManager getInstance() {
            return WebViewFactory.getProvider().getCookieManager();
    }

    看看WebViewFactorygetProvider()方法

    static WebViewFactoryProvider getProvider() {
            synchronized (sProviderLock) {
                if (sProviderInstance != null) return sProviderInstance;
      //....
                if (sProviderInstance == null) {
                    sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY,
                            WebViewFactory.class.getClassLoader());
                    if (sProviderInstance == null) {
                        sProviderInstance = new WebViewClassic.Factory();
                    }
                }
                return sProviderInstance;
            }
        }

    是通过getFactoryByName获取的,DEFAULT_WEBVIEW_FACTORY定义例如以下:

    private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory";

    所以实现上是获取了WebViewClassic.Factory对象。

    WebViewClassic.Factory对象的getCookieManager()例如以下:

    public CookieManager getCookieManager() {
           return CookieManagerClassic.getInstance();
    }

     总结一下:getInstance()得到了一个CookieManagerClassic对象。

    class CookieManagerClassic extends CookieManager

    CookieManagerClassic 继承自CookieManager,并覆写了CookieManager中的大部分方法。

    并终于通过本地方法实现详细的操作:

    比如步骤二中的acceptCookie()方法,

    public synchronized boolean acceptCookie() {
            return nativeAcceptCookie();
    }
    private static native boolean nativeAcceptCookie();
    还有步骤三中flushCookieStore()方法。

    protected void flushCookieStore() {
            nativeFlushCookieStore();
    }
    private static native void nativeFlushCookieStore();
    CookieManager中另一些经常使用的方法,比如:

    removeSessionCookie() 

    getCookie()

    removeAllCookie()

    setCookie()

    setAcceptCookie()

    ...

    CookieManager中的方法大多会MustOverrideException异常。所以必须用一个类来继承它。正如上面的CookieManagerClassic 

    关于BrowserSettings

    BrowserSettings是整个浏览器配置的管理类。先看initialize()方法。

    public static void initialize(final Context context) {
            sInstance = new BrowserSettings(context);
    }
    private BrowserSettings(Context context) {
            mContext = context.getApplicationContext();	 //获取应用的Context对象
     	  //获取应用的SharedPreferences
            mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            mAutofillHandler = new AutofillHandler(mContext);
            mManagedSettings = new LinkedList<WeakReference<WebSettings>>();
            mCustomUserAgents = new WeakHashMap<WebSettings, String>();
            mAutofillHandler.asyncLoadFromDb();
            BackgroundHandler.execute(mSetup);
    }

    initialize()实际上就是new了一个BrowserSettings对象。AutofillHandler是关于账户个人信息的,asyncLoadFromDb方法是异步来载入个人账户信息的,关于这部分后面会简单提到。

    创建了LinkedListWeakHashMap中都用到了WebSettings。看看WebSettings是什么吧。

     WebSettings在package android.webkit下,是一个抽象类。它用于管理WebView的配置状态。当一个WebView第一次被创建时,将会获得默认的配置集合,默认的配置将会通过调用随意的getter方法返回。通过WebView.getSettings()获取的WebSettings与WebView的生存周期结合在一起。假设一个WebView被销毁了。不论什么关于WebSettings的方法将会抛出IllegalStateException异常。

     WebSettings中的方法大多会MustOverrideException异常,所以必须用一个类来继承它。framework中是用WebSettingsClassic 继承它的。

    关于这点后面再讲。

    public class WebSettingsClassic extends WebSettings

     这个LinkedList是在方法startManagingSettings(WebSettings settings) 加入条目的,在stopManagingSettings(WebSettings settings)中删除条目,在syncManagedSettings()中同步每个WebSettings。

    这个WeakHashMap 是与用户代理相关的,通过WebSettings的setUserAgentString()来为WebView设置代理。

    再看这句:BackgroundHandler.execute(mSetup);

    BackgroundHandler的代码例如以下:

    public class BackgroundHandler {
     
        static HandlerThread sLooperThread;
        static ExecutorService mThreadPool;
     
        static {
            sLooperThread = new HandlerThread("BackgroundHandler", HandlerThread.MIN_PRIORITY);
            sLooperThread.start();
            mThreadPool = Executors.newCachedThreadPool();
        }
     
        public static void execute(Runnable runnable) {
            mThreadPool.execute(runnable);
        }
     
        public static Looper getLooper() {
            return sLooperThread.getLooper();
        }
     
        private BackgroundHandler() {}
    }

    整个BackgroundHandler 能够看成两个部分。一个线程池ExecutorService 对象,一个HandlerThread 对象。

    所以它的作用主要是两个:

    1、利用线程池ExecutorService 对象来运行线程Runnable对象。

    比如:BackgroundHandler.execute(mSetup); //mSetup是一个Runnable对象

    2、利用HandlerThread 来获取Looper对象,用于创建接收在其它线程中发送的消息的Handler对象。

    比如:

    Handler mForegroundHandler = new Handler();
    Handler mBackgroundHandler = new Handler(BackgroundHandler.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    
                }
    };
    private Runnable mCreateState = new Runnable() {
            	@Override
            	public void run() {
             Message.obtain(mBackgroundHandler, what, obj).sendToTarget();
            	}
    };

    知道了BackgroundHandler ,就知道了BackgroundHandler.execute(mSetup);就是在线程池中运行了mSetup这个Runnable对象。

    看看mSetup的定义:

    private Runnable mSetup = new Runnable() {
     
            @Override
            public void run() {
                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
                mFontSizeMult = metrics.scaledDensity / metrics.density;
     
                if (ActivityManager.staticGetMemoryClass() > 64) {
                    mPageCacheCapacity = 5;
                }
                mWebStorageSizeManager = new WebStorageSizeManager(mContext,
                        new WebStorageSizeManager.StatFsDiskInfo(getAppCachePath()),
                        new WebStorageSizeManager.WebKitAppCacheInfo(getAppCachePath()));
                mPrefs.registerOnSharedPreferenceChangeListener(BrowserSettings.this);
                //...
                sFactoryResetUrl = mContext.getResources().getString(R.string.homepage_base);
                //...
                synchronized (BrowserSettings.class) {
                    sInitialized = true;
                    BrowserSettings.class.notifyAll();
                }
            }
    };

    主要做了例如以下几件事:
    1、获取字体缩放因子(metrics.scaledDensity(字体缩放比例)/metrics.density(显示密度))
    2、依据ActivityManager.staticGetMemoryClass()的值设置缓存页面的数量,默认是1,看看staticGetMemoryClass()
        public static int staticGetMemoryClass() {
            // Really brain dead right now -- just take this from the configured
            // vm heap size, and assume it is in megabytes and thus ends with "m".
            String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
            if (vmHeapSize != null && !"".equals(vmHeapSize)) {
                return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
            }
            return staticGetLargeMemoryClass();
        }
    能够看出虚拟机堆的大小是从"dalvik.vm.heapgrowthlimit"读出来的,上面的程序段显示假设堆的大小大于64M,则将缓存页面的值设为5。否则默觉得1,这样能够避免OOM


    3、创建一个WebStorageSizeManager对象。用以管理缓存的磁盘空间

    4、为应用的SharedPreference注冊改变的监听器,里面的配置值改变将会实现同步设置操作

     	 @Override
        public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key) {
            syncManagedSettings();
    }

    5、获取浏览器的主页URL的路径sFactoryResetUrl 

    6、通知大家初始化完成。


    再来看看AutofillHandler ,Google用来实现表单自己主动填充功能的。Android浏览器的自己主动填充条目有:Full nameCompany nameAddressZip codeCountryPhoneEmail。AutoFillProfileDatabase 类是用来对这些条目进行存储的数据库操作类。

    数据将被存放在autofill.db 数据库中。

    使用自己主动填充功能须要注意下面几个方面:

    1、由于涉及到隐私,须要在浏览器的设置中开启Form Auto-fill选项;

    2、用户须要比較勤快,事先要将上面的个人信息录入;

    3、须要站点的支持,这些信息怎样和表单上的字段相应,这就是RFC 3106所定义的,表单控件的命名须要遵守规范。国内的站点。包含京东、亚马逊中国、当当、淘宝等均不支持。

    4、某些站点可能会试图捕获隐藏字段或难以发现的字段中的信息。因此,请勿在您不信任的站点上使用自己主动填充功能。

    5、某些站点会阻止浏览器保存您输入的内容。因此,无法在这些站点上填写表单。

    由上,表单自己主动填充功能基本上在国内是用不上的。

     我们还是简单的看一看它的实现,前面看到它的异步载入方法asyncLoadFromDb() .

    public void asyncLoadFromDb() {
             	 new LoadFromDb().start();
        }
    启用了一个线程。

    private class LoadFromDb extends Thread {
     
            @Override
            public void run() {
                SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(mContext);
     
                // 从SharedPreferences中读出近期使用的自己主动填充条目的ID.
                mAutoFillActiveProfileId = p.getInt(
                        PreferenceKeys.PREF_AUTOFILL_ACTIVE_PROFILE_ID,
                        mAutoFillActiveProfileId);
     	   //获取存储数据的数据库操作管理对象
                AutoFillProfileDatabase autoFillDb = AutoFillProfileDatabase.getInstance(mContext);
                Cursor c = autoFillDb.getProfile(mAutoFillActiveProfileId);
     
                if (c.getCount() > 0) {
                    c.moveToFirst();
     
                    String fullName = c.getString(c.getColumnIndex(
                            AutoFillProfileDatabase.Profiles.FULL_NAME));
      //从cursor中获取全部的字段的值
                    ...
                    mAutoFillProfile = new AutoFillProfile(mAutoFillActiveProfileId,
                            fullName, email, company, addressLine1, addressLine2, city,
                            state, zip, country, phone);
                }
                c.close();
                autoFillDb.close();
                mLoaded.countDown();
     //假设没有值。从联系人数据库中取值
                if (mAutoFillProfile == null) {
                    final Uri profileUri = Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
                            ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
                    String name = getContactField(profileUri,
                            ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
                    if (name != null) {
                        String email = getContactField(profileUri,
                                ContactsContract.CommonDataKinds.Email.ADDRESS,
                                ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
                        ...
     
                        synchronized(AutofillHandler.this) {
                            if (mAutoFillProfile == null) {
                                setAutoFillProfile(new AutoFillProfile(1, name, email, company,
                                        null, null, null, null, null, null, phone), null);
                            }
                        }
                    }
                }
        }
    详细的操作就不用多说了,就是简单的数据库操作。我们看到一个mLoaded 。定义例如以下
    private CountDownLatch mLoaded = new CountDownLatch(1);

    CountDownLatch 主要用于在完毕一组正在其它线程中运行的操作之前,它同意一个或多个线程一直等待。

     主要方法

     public CountDownLatch(int count);

     public void countDown();

     public void await() throws InterruptedException

    --CountDownLatch(int count)构造方法传了指定计次的数目。

    --countDown方法,当前线程调用此方法,则计数减一

    --await方法,调用此方法会一直堵塞当前线程,直到计时器的值为0

    程序中计次数目为1。此处调用countDown()是为了减1来唤醒获取AutoFillProfile之前堵塞的线程mSetup 。

    为什么是mSetup ?由于在BrowserSettings的构造方法中有例如以下的代码。

    private BrowserSettings(Context context) {
            ...
            mAutofillHandler.asyncLoadFromDb();
            BackgroundHandler.execute(mSetup);
    }

    BackgroundHandler.execute(mSetup)是在另外的线程中运行的操作。

    mSetup中注冊了SharedPreference的监听器。在onSharedPreferenceChanged中依次调用syncManagedSettings()-->syncSetting(settings)--> settings.setAutoFillProfile(getAutoFillProfile());

    getAutoFillProfile()是调用mAutofillHandler 的getAutoFillProfile()

     public AutoFillProfile getAutoFillProfile() {
            return mAutofillHandler.getAutoFillProfile();
    }

    mAutofillHandler 的getAutoFillProfile()定义例如以下:

    public synchronized AutoFillProfile getAutoFillProfile() {
            waitForLoad();
            return mAutoFillProfile;
    }

    再看waitForLoad()方法:

    private void waitForLoad() {
            try {
                mLoaded.await();
            } catch (InterruptedException e) {
                Log.w(LOGTAG, "...");
            }
    }

    这里用到了CountDownLatch 的await()方法来是线程堵塞,整个这一段的逻辑就是在构造BrowserSettings是在主线程载入表单自己主动填充,同步浏览器数据的操作是另开的线程中实现的,但有一个值须要主线程的操作完毕后才干获取,没有值的时候就会堵塞在那里,主线程操作结束获得值之后就会唤醒这个另开的线程,完毕值的存储工作。我用下图来表示。

          

    关于Preloader

    Preloader的initialize 方法也仅仅是创建了一个Preloader对象。没有进行其它的操作。

    Application是整个应用的入口,一般完毕一些初始化的操作,到这里就简单分析了一下。

  • 相关阅读:
    写在彻底转向有道云笔记一个月之后
    KMP算法实现
    有道云笔记 V.S. 为知笔记
    卸载印象笔记,跟印象笔记说拜拜
    ExpandRegion for Sublime Text:快速选择文本
    Linux cat命令详解
    Vim安装插件
    Vim与正则表达式
    还没供暖
    在Linux命令行中设置并使用代理服务器
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5077904.html
Copyright © 2020-2023  润新知