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是在主线程载入表单自己主动填充,同步浏览器数据的操作是另开的线程中实现的,但有一个值须要主线程的操作完毕后才干获取,没有值的时候就会堵塞在那里,主线程操作结束获得值之后就会唤醒这个另开的线程,完毕值的存储工作。我用下图来表示。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW5kcm9pZF9oYXNlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
关于Preloader
Preloader的initialize 方法也仅仅是创建了一个Preloader对象。没有进行其它的操作。
Application是整个应用的入口,一般完毕一些初始化的操作,到这里就简单分析了一下。
android原生browser分析(一)--Application的更多相关文章
- [Android Pro] android 4.4 Android原生权限管理:AppOps
reference : http://m.blog.csdn.net/blog/langzxz/45308199 reference : http://blog.csdn.net/hyhyl1990/ ...
- android 原生camera——设置模块修改
, 此篇博客是记一次客户需求修改,从上周五到现在正好一周时间,期间的各种酸爽,就不说了,还是来看大家关注的技术问题吧. 首先看下以前效果和修改后的效果: 修改前:修改后: 不知道有没有看明白,我在简单 ...
- Android源码分析-全面理解Context
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- 【android原生应用】之闹钟应用搭起篇
由于工作原因接触android开发一段时间了,对于开发有了一些了解,于是萌生了搭起android原生应用进行分析和学习的想法.先从闹钟应用开始吧. 1.首先要下载原生应用,原生应用在原生系统里面(当然 ...
- Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
- Android原生跳转React不同页面(undefined is not an object)
继续上篇文章的demo,由于现在的项目是原生的,打算用部分页面试下react native,那么问题来了:react貌似只有一个入口 index.android.js,那么在不同的原生页面需要跳转到不 ...
- Android原生(Native)C开发之四:SDL移植笔记
http://www.apkbus.com/forum.php?mod=viewthread&tid=1989 SDL(Simple DirectMedia Layer)是一套开放源码的跨平台 ...
- Android原生和H5交互;Android和H5混合开发;WebView点击H5界面跳转到Android原生界面。
当时业务的需求是这样的,H5有一个活动商品列表的界面,IOS和Android共用这一个界面,点击商品可以跳转到Android原生的商品详情界面并传递商品ID: 大概就是点击H5界面跳转到Androi ...
- Android HttpURLConnection源代码分析
Android HttpURLConnection源代码分析 之前写过HttpURLConnection与HttpClient的差别及选择.后来又分析了Volley的源代码. 近期又遇到了问题,想在V ...
随机推荐
- linux学习: sudo命令(ubuntu)
使用 sudo 命令可以提高命令的执行权限,以root权限执行 如 : sudo vi xxx 但是有些内置命令 如 cd 无法通过 sudo来执行 ,如 sudo cd xxx 这是会报错的. ...
- Office 365 - SharePoint 2013 Online之加入App开发工具Napa
1.新建一个站点集,模板选择开发者模板.例如以下图: 2.确定以后,须要稍等一会儿; 3.点击站点内容,加入app,例如以下图: 4.进入SharePoint Store.选择Napa.例如以下图: ...
- 死锁 android ANR
以下为一段ANR的LOG,主要是在WindowManagerService.java和ActivityManagerService.java中实现. W/WindowManager( 2183): K ...
- H5前端面试题及答案(2)
最近想着跳槽,但面试的邀约不多,内心有点烦躁.梳理梳理心情,跳槽季竞争也大,努力做好自己... 21.请设计一套方案,用于确保页面中js加载完全. <!doctype html> < ...
- Starting the application on Mac does not work(拷贝platforms到不同的位置,才能解决问题),还可设置DYLD_PRINT_LIBRARIES=1 观察动态库
In some rare cases it can happen that the application does not launch and there is no reaction after ...
- Docker学习笔记(4) — 开启Docker远程访问
默认情况下,Docker守护进程会生成一个socket(/var/run/docker.sock)文件来进程本地进程通信,而不会监听任何端口,因此只能在本地使用docker客户端或者使用Docker ...
- uoj Goodbye Jiawu
这次比赛真是太伤我心了. 比(惨)赛(不)结(忍)果(睹) 完挂感言 uoj round 5已经挂了一次了,没想到还要再挂第二次. 这次比赛的期望得分是\(100+100+100+70+10\)的.没 ...
- 《高质量程序设计指南:C++/C语言》面试题整理
本试题仅用于考查C++/C程序员的基本编程技能.内容限于C++/C常用 语法,不涉及 数据结构. 算法以及深奥的语法.考试成绩能反映出考生的编程质量以及对C++/C的理解程度,但不能反映考生的智力和软 ...
- 为经典版eclipse增加web and JavaEE插件
链接地址:http://jingyan.baidu.com/article/f25ef2546cd0e2482d1b825d.html 为经典版eclipse增加web and JavaEE插件 百度 ...
- 用c#开发微信(10) JSSDK 基本用法 分享接口“发送到朋友”
微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包.通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统的能力,同时可以直接使用微信分享. ...