首页 > 代码库 > android原生browser分析(一)--Application
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法,CookieSyncManager是WebSyncManager 的实现类并覆写了该方法。WebSyncManager中默认的同步时间间隔是5分钟,也就是Cookies的同步周期,WebSyncManager在创建的时候就会启动自身的线程,并按照同步周期来对cookies做同步,同步的具体实现即是syncFromRamToFlash()方法,WebSyncManager类中有以下几个重要的方法:
resetSync() 重新同步,即清除消息队列的同步消息,重新发送延时5分钟的延时同步消息。
startSync() 开始同步,即参考次数为0时,发送延时5分钟的延时同步消息,次数加1.
stopSync() 停止同步,即清除消息队列的同步消息
sync() 立即同步,即清除消息队列的同步消息,重新发送延时100毫秒的延时同步消息。
我们来看看CookieSyncManager中覆写WebSyncManager 中的syncFromRamToFlash方法,这个方法也就是cookies同步的方法。同步数据从RAM到FLASH。
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(); }
看看WebViewFactory的getProvider()方法
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方法是异步来加载个人账户信息的,关于这部分后面会简单提到。
创建了LinkedList和WeakHashMap中都用到了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 name、Company name、Address、Zip code、Country、Phone、Email等,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是整个应用的入口,一般完成一些初始化的操作,到这里就简单分析了一下。