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方法是异步来加载个人账户信息的,关于这部分后面会简单提到。

创建了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 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是整个应用的入口,一般完成一些初始化的操作,到这里就简单分析了一下。

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

时间: 2024-08-09 21:40:39

android原生browser分析(一)--Application的相关文章

android原生browser分析(二)--界面篇

我们先看一张浏览器的主界面,上面标示浏览器界面各部分对应的类,这里是以平板上的界面为例.给张图是为了给大家一个直观的感觉. BrowserActivity是整个应用的主界面,在onCreate中创建了Controller对象,Controller对象是整个应用最重要的管理类,这个后面再说. @Override public void onCreate(Bundle icicle) { mController = createController(); } Controller的创建中新建了UI类

Android——锁定launch - 原生Browser启动 -引导provision

前段时间做了一个功能,就是锁定主launch,机器上只能跑我们定义的launch,当时没注意影响, 最近发现就是因为在AMS中加了这个锁定过滤条件导致原生Browser无法启动了, 把我郁闷的,当时怎么想都觉得奇怪,这完全不相关的两件事怎么会影响到- 这里记录一下 撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/details/41015941 锁定主launch 启动android系统launch的过程原理可参考Android--启动过程详解中

android 原生应用、Web应用、混合应用优缺点分析

最近开发几个项目,牵涉到android的几种开发模式.对于原生态开发.web 应用开发以及混合模式开发,本人认为并不是哪一种就是最好的,哪一种就是最差的,这个完全是根据自己的需求,选择一种合适的开发模式.他们同时具备自己的有点,同时也有自身的缺点,我们根据实际情况,取其中的优点,尽量避免掉缺点,才是最好的开发模式.下面,我们就一同看看,这三种开发模式,到底有什么区别. 一.原生应用 (也称本地开发 Native App) 你使用过微软PowerPoint 或者 Word吧?这些可直接在你电脑上运

Android源码分析之SharedPreferences

在Android的日常开发中,相信大家都用过SharedPreferences来保存用户的某些settings值.Shared Preferences 以键值对的形式存储私有的原生类型数据,这里的私有的是指只对你自己的app可见的,也就是说别的app是无法访问到的. 客户端代码为了使用它有2种方式,一种是通过Context#getSharedPreferences(String prefName, int mode)方法, 另一种是Activity自己的getPreferences(int mo

android 文件系统目录分析(手机系统目录分析)

# pwd && ls -a -l / drwxrwxrwt root     root              2009-06-10 09:53 sqlite_stmt_journals drwxrwx--- system   cache             2008-09-06 22:51 cache d---rwxrwx system   system            1970-01-01 08:00 sdcard lrwxrwxrwx root     root    

Cordova Android源码分析系列一(项目总览和CordovaActivity分析)

PhoneGap/Cordova是一个专业的移动应用开发框架,是一个全面的WEB APP开发的框架,提供了以WEB形式来访问终端设备的API的功能.这对于采用WEB APP进行开发者来说是个福音,这可以避免了原生开发的某些功能.Cordova 只是个原生外壳,app的内核是一个完整的webapp,需要调用的原生功能将以原生插件的形式实现,以暴露js接口的方式调用. Cordova Android项目是Cordova Android原生部分的Java代码实现,提供了Android原生代码和上层We

wex5 教程 之 web网站android原生模式打包

如果有成型的web网站,想做成手机app,如何用wex5来打包成apk呢?比如说百度视频,我想打包成自已的apk安装到手机上,怎么做呢? 官方提供了四种打包模式,都需要提供服务地址,也就是说要有一台服务器来提供服务.我只是要把web地址封装一下,apk打开后跳转到网页就行,显然服务地址是不需要的. 那如果用wex5的页面frame组件加载一个web页面呢? 经测试,这种方法可行,问题是,w页面是wex5自创的页面,不是html的document页面,会出现视频格式不能播放,无falsh插件问题.

Android系统启动流程分析

随着Android版本的升级,aosp项目中的代码也有了些变化,本文基于Android 7.0分析Android系统启动流程. 简单来说Android系统启动大体如下: init进程 和所有Linux系统一样,Android系统的启动同样是从init进程启动.init进程会解析init.rc文件(关于init.rc中的语法,可以参见我之前写的深入分析AIL语言及init.rc文件),加载相关目录,并启动相关服务 init进程在/system/core/init/init.c init.rc文件在

【android原生应用】之闹钟应用搭起篇

由于工作原因接触android开发一段时间了,对于开发有了一些了解,于是萌生了搭起android原生应用进行分析和学习的想法.先从闹钟应用开始吧. 1.首先要下载原生应用,原生应用在原生系统里面(当然你得先下载原生的系统,过程请百度之). 目录如下:packages\apps,所有的原生基础应用都在这个里面,我们进入DeskClock目录,将其作为一个工程搭建起来. 这时候会报错,根据报错信息来看是由于缺少jar包近期的,datetimepicker.jar .android-support-v