跳到主要内容

Android webview交互性能监测指标获取方法

· 阅读需 7 分钟

业界衡量移动web app交互性能的优劣主要是通过监测webview渲染页面时白屏时间,DOM树构建时间,整页时间和首屏时间这三个指标来完成的,那么这四个指标分别的意义是什么呢?我们从w3c提供的navigation Timing中看到交互性能指的是Processing和onLoad这两部分的时间。

timing-overview.png

在浏览器交互阶段(Processing和onLoad时间段)浏览器接收服务器返回的基础页数据后,浏览器需要对HTML这个单纯的文本内容进行解析,从文本中构建出一个内部数据结构,叫做DOM树(DOM tree),用于组织将要绘制在屏幕上的内容。从HTML也能得到外联或内联的CSS脚本和JavaScript脚本,当然还有媒体文件,比如图片、视频、声音,这些都需要再次发起网络请求下载。CSS文本内容中的规则同样会被构建成一个内部数据结构,叫做CSS树(CSS tree),来决定DOM树的节点在屏幕上的布局、颜色、状态效果。JavaScript脚本被触发执行后,除了计算业务,往往还需要操作DOM树,就是所谓的DOM API。

webkitflow.png

  • 白屏时间 ***指浏览器开始显示内容的时间。***但是在传统的采集方式里,是在HTML的head标签结尾里记录时间戳,来计算白屏时间。在这个时刻,浏览器开始解析body标签内的内容。而现代浏览器不会等待CSS树(所有CSS文件下载和解析完成)和DOM树(整个body标签解析完成)构建完成才开始绘制,而是马上开始显示中间结果。所以经常在低网速的环境中,观察到页面由上至下缓慢显示完,或者先显示文本内容后再重绘成带有格式的页面内容。在android中我们通过使用webview.WebChromeClientonReceivedTitle事件来近似获得白屏时间。

  • DOM树构建时间 指浏览器开始对基础页文本内容进行解析到从文本中构建出一个内部数据结构(DOM树)的时间,这个事件是从HTML中的onLoad的延伸而来的,当一个页面完成加载时,初始化脚本的方法是使用load事件,但这个类函数的缺点是仅在所有资源都完全加载后才被触发,这有时会导致比较严重的延迟,开发人员随后创建了domready事件,它在DOM加载之后及资源加载之前被触发。domready被众多JavaScript库所采用,它在本地浏览器中以DOMContentLoaded事件的形式被使用。在android中我们通过注入js代码到webview中的方式来实现;具体实现上,在WebChromeClientonReceivedTitle事件被触发时注入我们的js代码,然后通过WebChromeClientonJsPrompt事件来获取domc(window.DOMContentLoaded事件)时间。

@Override
public void onReceivedTitle (WebView view, String title) {
view.loadUrl("javascript:" +
"window.addEventListener('DOMContentLoaded', function() {" +
"prompt('domc:' + new Date().getTime());" +
);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
if(message != null){
if(!preCacheRun){
String[] strs = message.split(":");
if(2 == strs.length){
if("domc".equals(strs[0])){
result.getCurrentRun().setDocComplete(Long.valueOf(strs[1].trim()));
}
}
}
}
r.confirm(defaultValue);
return true;
}
  • 首屏时间 指从网页应用的角度定义的指标,在Navigation Timing或者浏览器实现中并没有相关指标值。首屏时间,***是指用户看到第一屏,即整个网页顶部大小为当前窗口的区域,显示完整的时间。***常用的方法有,页面标签标记法、图像相似度比较法和首屏高度内图片加载法;
  1. 页面标签标记法,在HTML文档中对应首屏内容的标签结束位置,使用内联的JavaScript代码记录当前时间戳,比较局限;
  2. 图像相似度比较法,通过比较连续截屏图像的像素点变化趋势确定首屏时间,最为科学和直观的方式,但是比较消耗本地设备的运行资源;
  3. 首屏高度内图片加载法,通过寻找首屏区域内的所有图片,计算它们加载完的时间去得到首屏时间,这样比较符合网页的实际体验并且比较节省设备运行资源; 具体实现上我采用的是最后一种,即"首屏高度内图片加载法";因为通常需要考虑首屏时间的页面,都是因为在首屏位置内放入了较多的图片资源。现代浏览器处理图片资源时是异步的,会先将图片长宽应用于页面排版,然后随着收到图片数据由上至下绘制显示的。并且浏览器对每个页面的TCP连接数限制,使得并不是所有图片都能立刻开始下载和显示。因此我们在DOM树构建完成后即可遍历获得所有在设备屏幕高度内的所有图片资源标签,在所有图片标签中添加document.onload事件,在整页加载完成(window.onLoad事件发生)时遍历图片标签并获得之前注册的document.onload事件时间的最大值,该最大值减去navigationStart即认为近似的首屏时间。在android中我们通过注入js代码到webview中的方式来实现;具体实现上,在WebChromeClient的onReceivedTitle事件被触发时注入我们的js代码,然后通过WebChromeClientonJsPrompt事件来获取firstscreen时间。

js部分计算首屏时间的逻辑代码:

function first_screen () {
var imgs = document.getElementsByTagName("img"), fs = +new Date;
var fsItems = [], that = this;
function getOffsetTop(elem) {
var top = 0;
top = window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop;
try{
top += elem.getBoundingClientRect().top;
}catch(e){

}finally{
return top;
}

}
var loadEvent = function() {
//gif避免
if (this.removeEventListener) {
this.removeEventListener("load", loadEvent, false);
}
fsItems.push({
img : this,
time : +new Date
});
}
for (var i = 0; i < imgs.length; i++) {
(function() {
var img = imgs[i];

if (img.addEventListener) {

!img.complete && img.addEventListener("load", loadEvent, false);
} else if (img.attachEvent) {

img.attachEvent("onreadystatechange", function() {
if (img.readyState == "complete") {
loadEvent.call(img, loadEvent);
}

});
}

})();
}
function firstscreen_time() {
var sh = document.documentElement.clientHeight;
for (var i = 0; i < fsItems.length; i++) {
var item = fsItems[i], img = item['img'], time = item['time'], top = getOffsetTop(img);
if (top > 0 && top < sh) {
fs = time > fs ? time : fs;
}
}
return fs;
}
window.addEventListener('load', function() {
prompt('firstscreen:' + firstscreen_time());
});
}

webview的注入代码:

private void registerOnLoadHandler(WebView view) {
String jscontent = getJavaScriptAsString();
view.loadUrl("javascript:" + jscontent +
"window.addEventListener('DOMContentLoaded', function() {" +
"first_screen();
});"
);
}

@Override
public void onReceivedTitle (WebView view, String title) {
registerOnLoadHandler(view);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
if(message != null){
if(!preCacheRun){
String[] strs = message.split(":");
if(2 == strs.length){
if("firstscreen".equals(strs[0])){
result.getCurrentRun().setFirstScreen(Long.valueOf(strs[1].trim()));
}
}
}
}
r.confirm(defaultValue);
return true;
}
  • 整页时间 ***指页面完成整个加载过程的时刻。***从Navigation Timing API上采集,就是loadEventEnd减去navigationStart。在传统采集方法中,会使用window对象的onload事件来记录时间戳,它表示浏览器认定该页面已经载入完全了。android中我们通过注入js代码到webview中的方式来实现;具体实现上,在WebChromeClient的onReceivedTitle事件被触发时注入我们的js代码,然后通过WebChromeClientonJsPrompt事件来获取load(window.onLoad事件)时间。
@Override
public void onReceivedTitle (WebView view, String title) {
view.loadUrl("javascript:" +
"window.addEventListener('load', function() {" +
"prompt('load:' + new Date().getTime());" +
);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
if(message != null){
if(!preCacheRun){
String[] strs = message.split(":");
if(2 == strs.length){
if("load".equals(strs[0])){
result.getCurrentRun().setFullyLoaded(Long.valueOf(strs[1].trim()));
}
}
}
}
r.confirm(defaultValue);
return true;
}

java 数组

· 阅读需 1 分钟

一维数组

数组初始化
//静态初始化
int[] a = {1,2,3};
int[] a = new int[]{1,2,3};

//动态初始化
int[] a = new int[3];
数组遍历
//普通for循环
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}

//增强for循环
for(int i:a){
System.out.println(i);
}

二维数组

初始化
//静态初始化
int[][] a = {{1,2},{3,4}};

//动态初始化
int[][] a = new int[3][3];
int[][] a = new int[3][];
a[0] = new int[3];

数组复制

System.arraycopy(src, srcPos, dest, destPos, length);

数组排序

Arrays.sort(a);

数组查找

//二分查找(前提是已排序)
Arrays.binarySearch(a, key);

java集合

· 阅读需 5 分钟
  • 所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。

  • Collection接口派生的两个常用接口是List和Set。

  • List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组,List允许有相同的元素。

  • 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack

List接口

LinkedList类

LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。 注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: List list = Collections.synchronizedList(new LinkedList(...));

ArrayList类

ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。 size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。 和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

Vector类

Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

Stack 类

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。 请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口

请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Hashtable类

Hashtable继承Map接口,Hashtable是同步的,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。

作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。

如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。

HashMap类

HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。

WeakHashMap类

WeakHashMap是一种改进的HashMap,它对key实行"弱引用",如果一个key不再被外部所引用,那么该key可以被GC回收。

总结

  • 如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  • 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  • 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  • 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程.

java 面向对象——抽象类与接口

· 阅读需 1 分钟

抽象类

抽象类只是比普通类多了一些抽象方法而已,抽象方法没有方法体。

abstract class Person{
public abstract void walk();
}

注意:

  • 抽象类不能直接实例化(new Person()是错误的),只能通过子类实例化
  • 抽象类也包含构造方法(子类调用父类构造方法)
  • 抽象类也可以包含普通方法

接口

接口是抽象类的更一步抽象,接口中所有的方法都是抽象方法

interface Person{
public void walk();
public void eat();
}

注意:

  • 接口不能被实例化
  • 接口没有构造方法
  • 接口中的方法必须是抽象方法(jdk1.8后可以有default方法)
  • 接口中的常量必须是public static final

抽象类与接口的区别

抽象类接口
成员变量无特殊要求必须是public static final
构造方法
方法可以有抽象方法也可以有普通方法只能是抽象方法(jdk1.8后可以有default)
继承只能单继承可以多实现
成员可以有普通成员只能是常量

java 面向对象——类与对象的概念和使用

· 阅读需 2 分钟

方法

方法就是一段可重复调用的代码段

方法重载

方法的重载:方法名称相同,但是参数的类型和个数不同,通过传递参数的个数和类型不同来完成不同的功能。

方法重写

父类与子类之间的多态性,对父类的函数进行重新定义。如果子类中定义某方法与其父类有相同的名称和参数,我们说这个方法被重写,方法重写又称方法覆盖。

方法递归

一种特殊的调用形式,就是方法自己调用自己。

public static int add(int num){
if(num == 1){
return 1;
}else{
return num+add(num-1);
}
}

类是对某一类事物的描述,是抽象的、概念上的意义,对象是实际存在的该类事物的每一个个体,也被称为实例。

内存分配
Person persion = new Person();

面向对象最重要的特征

  • 封装,对外部不可见
  • 继承,扩展类的功能
  • 多态,方法的重载,对象的多态性,面向对象的精髓所在
封装性

使用关键字private修饰属性和方法

多态性
  • 向上转型,父类对象 = 子类实例
  • 向下转型,子类对象 = (子类)父类实例
匿名对象

匿名对象就是没有名字的对象,如果程序只是用一次该对象,就可以使用匿名对象的方式。

构造方法
  • 构造方法名称必须与类名一致
  • 构造方法没有返回值
  • 每个类实例化之后都会调用构造方法,如果没有构造方法,程序在编译的时候会创建一个无参的构造方法
  • 构造方法可以重载

Android基础

· 阅读需 2 分钟

ThreadLocal

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

java.lang.ThreadLocal<T>的具体实现

先来看一下ThreadLocal的set()方法的源码是如何实现的:

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

通过getMap获取当前线程的ThreadLocalMap,然后将变量设置到这个ThreadLocalMap中,如果ThreadLocalMap为空时,则通过createMap方法创建。

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象。

AndroidManifest解析

· 阅读需 6 分钟

关于AndroidManifest.xml

AndroidManifest.xml 是每个android程序中必须的文件。它位于整个项目的根目录,描述了package中暴露的组件(activities, services, 等等),他们各自的实现类,各种能被处理的数据和启动位置。 除了能声明程序中的Activities, ContentProviders, Services, 和Intent Receivers,还能指定permissions和instrumentation。

各个节点的详细介绍

(<manifest>)属性
<manifest  xmlns:android="http://schemas.android.com/apk/res/android"
package="com.woody.test"
android:sharedUserId="string"
android:sharedUserLabel="string resource"
android:versionCode="integer"
android:versionName="string"
android:installLocation=["auto" | "internalOnly | preferExternal"] >
</manifest>
  • sharedUserId,表明数据权限,因为默认情况下,Android给每个APK分配一个唯一的UserID,所以是默认禁止不同APK访问共享数据的。若要共享数据,第一可以采用Share Preference方法,第二种就可以采用sharedUserId了,将不同APK的sharedUserId都设为一样,则这些APK之间就可以互相共享数据了
  • sharedUserLabel,一个共享的用户名,它只有在设置了sharedUserId属性的前提下才会有意义
  • versionCode,给设备程序识别版本(升级)用的必须是一个interger值代表app更新过多少次
  • versionName,这个名称是给用户看的,你可以将你的APP版本号设置为1.1版
  • installLocation,安装参数,是Android2.2中的一个新特性,installLocation有三个值可以选择:internalOnly、auto、preferExternal。
    • preferExternal,系统会优先考虑将APK安装到SD卡上(当然最终用户可以选择为内部ROM存储上,如果SD存储已满,也会安装到内部存储上)
    • auto,系统将会根据存储空间自己去适应
    • internalOnly,是指必须安装到内部才能运行
(< Application >)属性
<application  android:allowClearUserData=["true" | "false"]
android:allowTaskReparenting=["true" | "false"]
android:backupAgent="string"
android:debuggable=["true" | "false"]
android:description="string resource"
android:enabled=["true" | "false"]
android:hasCode=["true" | "false"]
android:icon="drawable resource"
android:killAfterRestore=["true" | "false"]
android:label="string resource"
android:manageSpaceActivity="string"
android:name="string"
android:permission="string"
android:persistent=["true" | "false"]
android:process="string"
android:restoreAnyVersion=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme" >
</application>
  • android:allowClearUserData,用户是否能选择自行清除数据,默认为true,程序管理器包含一个选择允许用户清除数据。当为true时,用户可自己清理用户数据,反之亦然
  • android:allowTaskReparentin,是否允许activity更换从属的任务,比如从短信息任务切换到浏览器任务
  • android:debuggable,当设置为true时,表明该APP在手机上可以被调试。默认为false,在false的情况下调试该APP
  • android:description,可以用于具体描述获取该许可的程序可以做哪些事情
  • android:label,应用名称
  • android:enabled,Android系统是否能够实例化该应用程序的组件
  • android:icon,APP的图标
  • android:name,为应用程序所实现的Application子类的全名。当应用程序进程开始时,该类在所有应用程序组件之前被实例化
  • android:permission,这个属性若在<application>上定义的话,是一个给应用程序的所有组件设置许可的便捷方式
  • android:presistent,该应用程序是否应该在任何时候都保持运行状态,默认为false
  • android:process,应用程序运行的进程名,它的默认值为<manifest>元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。如果你想两个应用程序共用一个进程的话,你可以设置他们的android:process相同,但前提条件是他们共享一个用户ID及被赋予了相同证书的时候
  • android:taskAffinity,拥有相同的affinity的Activity理论上属于相同的Task,应用程序默认的affinity的名字是<manifest>元素中设定的package名
  • android:theme,资源的风格
(< Activity >)属性
<activity android:allowTaskReparenting=["true" | "false"]
android:alwaysRetainTaskState=["true" | "false"]
android:clearTaskOnLaunch=["true" | "false"]
android:configChanges=["mcc", "mnc", "locale",
"touchscreen", "keyboard", "keyboardHidden",
"navigation", "orientation", "screenLayout",
"fontScale", "uiMode"]
android:enabled=["true" | "false"]
android:excludeFromRecents=["true" | "false"]
android:exported=["true" | "false"]
android:finishOnTaskLaunch=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:launchMode=["multiple" | "singleTop" |
"singleTask" | "singleInstance"]
android:multiprocess=["true" | "false"]
android:name="string"
android:noHistory=["true" | "false"]
android:permission="string"
android:process="string"
android:screenOrientation=["unspecified" | "user" | "behind" |
"landscape" | "portrait" |
"sensor" | "nosensor"]
android:stateNotNeeded=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme"
android:windowSoftInputMode=["stateUnspecified",
"stateUnchanged", "stateHidden",
"stateAlwaysHidden", "stateVisible",
"stateAlwaysVisible", "adjustUnspecified",
"adjustResize", "adjustPan"] >
</activity>
  • android:alwaysRetainTaskState,是否保留状态不变, 比如切换回home, 再从新打开,activity处于最后的状态。比如一个浏览器拥有很多状态(当打开了多个TAB的时候),用户并不希望丢失这些状态时,此时可将此属性设置为true
  • android:clearTaskOnLaunch,比如 P 是 activity, Q 是被P 触发的 activity, 然后返回Home, 重新启动 P,是否显示 Q
  • android:configChanges,正常情况下. 如果手机旋转了.当前Activity后杀掉,然后根据方向重新加载这个Activity. 就会从onCreate开始重新加载. 如果你设置了 这个选项, 当手机旋转后,当前Activity之后调用onConfigurationChanged() 方法. 而不跑onCreate方法等
  • android:excludeFromRecents,是否可被显示在最近打开的activity列表里,默认是false
  • android:finishOnTaskLaunch,当用户重新启动这个任务的时候,是否关闭已打开的activity,默认是false,如果这个属性和allowTaskReparenting都是true,这个属性就是王牌。Activity的亲和力将被忽略。该Activity已经被摧毁并非re-parented
  • android:launchMode,在多Activity开发中,有可能是自己应用之间的Activity跳转,或者夹带其他应用的可复用Activity。可能会希望跳转到原来某个Activity实例,而不是产生大量重复的Activity,默认为standard
    • standard:就是intent将发送给新的实例,所以每次跳转都会生成新的activity
    • singleTop:也是发送新的实例,但不同standard的一点是,在请求的Activity正好位于栈顶时(配置成singleTop的Activity),不会构造新的实例
    • singleTask:和后面的singleInstance都只创建一个实例,当intent到来,需要创建设置为singleTask的Activity的时候,系统会检查栈里面是否已经有该Activity的实例。如果有直接将intent发送给它
    • 首先说明一下task这个概念,Task可以认为是一个栈,可放入多个Activity,比如启动一个应用,那么Android就创建了一个Task,然后启动这个应用的入口Activity,那在它的界面上调用其他的Activity也只是在这个task里面。singleInstance模式就是将该Activity单独放入一个栈中,这样这个栈中只有这一个Activity,不同应用的intent都由这个Activity接收和展示,这样就做到了共享。当然前提是这些应用都没有被销毁,所以刚才是按下的HOME键,如果按下了返回键,则无效
  • android:multiprocess,是否允许多进程,默认是false

july-class-01(字符和字符串)

· 阅读需 4 分钟

Huffman编码

Huffman编码是一种无损压缩编码方案。

思想:根据源字符出现的概率对字符编码,概率高的字符使用较短的编码,概率低的使用较长的编码,从而使得编码后的字符串长度期望最小。

Huffman编码是一种贪心算法:每次总选择两个最小概率的字符节点合并

移除空格算法

时间复杂度O(n),空间复杂度O(1)


#include <iostream>
#include <stdio.h>
using namespace std;


void RemoveBlank(char* pString){
int j = 0;
for(int i= 0;pString[i]!='\0';i++){
if(pString[i] != ' '){
if(i != j){
pString[j] = pString[i];
}
j++;
}
}
pString[j] = 0;
}

int main(){
char str [] = "I have Dream o";
RemoveBlank(str);
cout<<str<<endl;
return 0;
}

找大数

#include <iostream>
#include <stdio.h>
using namespace std;
void FindMax(const int* a, int size,int& nMax,int& nSecondMax){
for(int i = 0;i<size;i++){
if(nMax < a[i]){
nSecondMax = nMax;
nMax = a[i];
}else if(nSecondMax < a[i]){
nSecondMax = a[i];
}
}
}

int main(){
int a [] = {1,5,4,8,3,2,9,14};
int nMax=0;
int nSecondMax = 0;
FindMax(a,sizeof(a)/sizeof(int),nMax,nSecondMax);
printf("max=%d,secondMax = %d\n",nMax,nSecondMax);
return 0;
}

Huffman 编码Java版

 public static class HuffmanNode {
public HuffmanNode() {
// TODO Auto-generated constructor stub
}

public int weight = 0;
public int parent = 0;
public int leftNode = 0;
public int rightNode = 0;
}

mian函数

public static void main(String[] args) {
// TODO Auto-generated method stub
String str = "when I was young I'd listen to the radio waiting for my favorite songs "
+ "when they played I'd sing along,"
+ "it make me smile."
+ "those were such happy times and not so long ago"
+ "how I wondered where they'd gone."
+ "but they're back again just like a long lost friend"
+ "all the songs I love so well."
+ "every shalala every wo'wo"
+ "still shines."
+ "every shing-a-ling-a-ling "
+ "that they're starting" + "to sing so fine";

int[] weight = new int[N];
ArrayList<Integer> list = new ArrayList<Integer>();
CalcFrequency(str, weight);
weight[(int) '\t'] = 0;
CalcExistChar(weight, list);
int N2 = list.size();
ArrayList<ArrayList<Character>> node = new ArrayList<ArrayList<Character>>(N2);
HuffmanCoding(weight, N2, node);
printNode(node,list);
System.out.println("complete");
}

计算字符权重

	static void CalcFrequency(String str, int[] arr) {
for (int i = 0; i < str.length(); i++) {
arr[(int) str.charAt(i)]++;
}
}

将字符记录在list中

	static void CalcExistChar(int[] arr, ArrayList<Integer> list) {
int j = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
list.add(i);
if (i != j) {
arr[j] = arr[i];
j++;
}
}
}
}

核心代码

static void HuffmanCoding(int[] weight, int N,
ArrayList<ArrayList<Character>> node) {
if (N <= 0)
return;
int m = 2 * N + 1;
// 初始化树
ArrayList<HuffmanNode> huffmanTree = new ArrayList<HuffmanNode>();
for (int i = 0; i < m; i++) {
HuffmanNode huffmanNode = new HuffmanNode();
huffmanTree.add(huffmanNode);
}
for (int i = 0; i < N; i++) {
huffmanTree.get(i).weight = weight[i];
}
// 找出2个权重最小将其组合
for (int i = N; i < m; i++) {
FindMin(huffmanTree, i);
if (s1 == -1 || s2 == -1)
continue;
huffmanTree.get(s1).parent = huffmanTree.get(s2).parent = i;
huffmanTree.get(i).leftNode = s1;
huffmanTree.get(i).rightNode = s2;
huffmanTree.get(i).weight = huffmanTree.get(s1).weight
+ huffmanTree.get(s2).weight;
}
//根据建好的huffman树从叶子到根,计算每个叶子节点的编码
int nParent;
int curNode;
for (int i = 0; i < N; i++) {
ArrayList<Character> cur = new ArrayList<Character>();
nParent = huffmanTree.get(i).parent;
curNode = i;
while (nParent != 0) {
if (huffmanTree.get(nParent).leftNode == curNode) {
cur.add('0');
} else {
cur.add('1');
}
curNode = nParent;
nParent = huffmanTree.get(curNode).parent;
}
Collections.reverse(cur);
node.add(cur);
}
}

打印树编码

	static void printNode(ArrayList<ArrayList<Character>> code,
ArrayList<Integer> charList) {
for (int i = 0; i < code.size(); i++) {
printCode((char) charList.get(i).intValue(), code.get(i));
}
}

static void printCode(char c, ArrayList<Character> code) {
System.out.println("char=" + c + ":");
for (int i = 0; i < code.size(); i++) {
System.out.print(code.get(i));
}
System.out.println();
}

july-class-02(树)

· 阅读需 2 分钟

二叉树遍历

前序遍历:根左右 中序遍历:左根右 后序遍历:左右根

层次遍历:使用队列

//先序遍历
void PreOrderTraverse(BiTree T){
if(T){
printf("%c",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//中序遍历
void InOrderTraverse(BiTree T){
if(T){
InOrderTraverse(T->lchild);
printf("%c",T->data);
InOrderTraverse(T->rchild);
}
}

//后序遍历
void PostOrderTraverse(BiTree T){
if(T){
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%c",T->data);
}
}

二叉树的深度

int Depth(BiTree T){
if(!T) return 0;
int d1=Depth(T->lchild);
int d2=Depth(T->rchild);
return (d1>d2?d1:d2)+1;
}

二叉树的节点数

int NodeCount(BiTree T){
if(!T) return 0;
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}

二叉树的叶子节点数

int LeafCount(BiTree T){
if(!T) return 0;
if(!T->lchild && !T->rchild) return 1;
return LeafCount(T->lchild)+LeafCount(T->rchild);
}

判断两颗二叉树是否相同

bool IsSameTree(BiTree T1,BiTree T2){
if(!T1 && !T2) return true;
if(!T1 || !T2) return false;
if(T1->data!=T2->data) return false;
return IsSameTree(T1->lchild,T2->lchild)&&IsSameTree(T1->rchild,T2->rchild);
}

july-class-03(查找与排序)

· 阅读需 2 分钟

折半查找

int BinarySearch(int *a,int n,int key){
int low=1,high=n,mid;
while(low<=high){
mid=(low+high)/2;
if(key<a[mid]){
high=mid-1;
}else if(key>a[mid]){
low=mid+1;
}else{
return mid;
}
}
return 0;
}

冒泡排序

void BubbleSort(int *a,int n){
int i,j,flag;
for(i=1;i<n;i++){
flag=0;
for(j=0;j<n-i;j++){
if(a[j]>a[j+1]){
a[j]=a[j]^a[j+1];
a[j+1]=a[j]^a[j+1];
a[j]=a[j]^a[j+1];
flag=1;
}
}
if(flag==0) break;
}
}

快速排序

void QuickSort(int *a,int low,int high){
if(low<high){
int pivotloc=Partition(a,low,high);
QuickSort(a,low,pivotloc-1);
QuickSort(a,pivotloc+1,high);
}
}

int Partition(int *a,int low,int high){
int pivotkey=a[low];
while(low<high){
while(low<high && a[high]>=pivotkey) --high;
a[low]=a[high];
while(low<high && a[low]<=pivotkey) ++low;
a[high]=a[low];
}
a[low]=pivotkey;
return low;
}

直接插入排序

void InsertSort(int *a,int n){
int i,j;
for(i=2;i<=n;i++){
if(a[i]<a[i-1]){
a[0]=a[i];
for(j=i-1;a[0]<a[j];j--){
a[j+1]=a[j];
}
a[j+1]=a[0];
}
}
}

希尔排序

void ShellSort(int *a,int n){
int i,j;
int dk=n/2;
while(dk>=1){
for(i=dk+1;i<=n;i++){
if(a[i]<a[i-dk]){
a[0]=a[i];
for(j=i-dk;j>0 && a[0]<a[j];j-=dk){
a[j+dk]=a[j];
}
a[j+dk]=a[0];
}
}
dk=dk/2;
}
}

堆排序

void HeapAdjust(int *a,int s,int m){
int rc=a[s];
for(int j=2*s;j<=m;j*=2){
if(j<m && a[j]<a[j+1]) j++;
if(rc>=a[j]) break;
a[s]=a[j];
s=j;
}
a[s]=rc;
}

void HeapSort(int *a,int n){
int i;
for(i=n/2;i>0;i--){
HeapAdjust(a,i,n);
}
for(i=n;i>1;i--){
a[1]=a[1]^a[i];
a[i]=a[1]^a[i];
a[1]=a[1]^a[i];
HeapAdjust(a,1,i-1);
}
}