监听android home键的实现方式

1.首先是创建一个广播接受者

private BroadcastReceiver mHomeKeyEventReceiver = new BroadcastReceiver() {
		String SYSTEM_REASON = "reason";
		String SYSTEM_HOME_KEY = "homekey";
		String SYSTEM_HOME_KEY_LONG = "recentapps";
		 
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
				String reason = intent.getStringExtra(SYSTEM_REASON);
				if (TextUtils.equals(reason, SYSTEM_HOME_KEY)) {
					 //表示按了home键,程序到了后台
					Toast.makeText(getApplicationContext(), "home", Toast.LENGTH_LONG).show();
				}else if(TextUtils.equals(reason, SYSTEM_HOME_KEY_LONG)){
					//表示长按home键,显示最近使用的程序列表
				}
			} 
		}
	};

2.注册监听

可以在Activity里注册,也可以在Service,Application里面

//注册广播
registerReceiver(mHomeKeyEventReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));

完整的代码如下:

import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.TextUtils;
import android.widget.Toast;

public class MainApplication extends Application {

    /**
     * 监听是否点击了home键将客户端推到后台
     */
    private BroadcastReceiver mHomeKeyEventReceiver = new BroadcastReceiver() {
        String SYSTEM_REASON = "reason";
        String SYSTEM_HOME_KEY = "homekey";
        String SYSTEM_HOME_KEY_LONG = "recentapps";

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
                String reason = intent.getStringExtra(SYSTEM_REASON);
                if (TextUtils.equals(reason, SYSTEM_HOME_KEY)) {
                    //表示按了home键,程序到了后台
                    Toast.makeText(getApplicationContext(), "home", Toast.LENGTH_LONG).show();
                }else if(TextUtils.equals(reason, SYSTEM_HOME_KEY_LONG)){
                    //表示长按home键,显示最近使用的程序列表
                }
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //注册广播
        registerReceiver(mHomeKeyEventReceiver, new IntentFilter(
                Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
    }

    @Override
    public void onTerminate() {
        unregisterReceiver(mHomeKeyEventReceiver);
        super.onTerminate();
    }
}

参考链接:监听android home键的实现方式

java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once)

在使用AsyncTask的时候,收到了如下的错误信息:

ERROR/AndroidRuntime(1936): FATAL EXCEPTION: main
ERROR/AndroidRuntime(1936): java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once)

查了一下文档,才了解到AsyncTask不可以复用。The task can be executed only once (an exception will be thrown if a second execution is attempted.)
AsyncTask文档链接AsyncTask。关键信息摘抄如下:

Threading rules


There are a few threading rules that must be followed for this class to work properly:

  • The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
  • The task instance must be created on the UI thread.
  • execute(Params...) must be invoked on the UI thread.
  • Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually.
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.)

JS获得element的index

获得某个element在parent中的index。

jquery提供了index接口,可以直接拿到。

如果没有jquery的话,

将document查询到的HTML Collection转为Array,

然后使用Array的index接口拿到index。

var ItemList = Array.prototype.slice.call( document.getElementsByClassName("item"));

var _currentFocus = document.getElementsByClassName("item focus")[0];
var _position = ItemList.indexOf(_currentFocus);

NDK 使用STL静态库注意,不要两个SO文件同时静态链接一个STL静态库

NDK 使用STL静态库注意,不要两个SO文件同时静态链接一个STL静态库,具体解释如下:

Static runtimes

Please keep in mind that the static library variant of a given C++ runtime SHALL ONLY BE LINKED INTO A SINGLE BINARY for optimal conditions.

What this means is that if your project consists of a single shared library, you can link against, e.g., stlport_static, and everything will work correctly.

On the other hand, if you have two shared libraries in your project (e.g. libfoo.so and libbar.so) which both link against the same static runtime, each one of them will include a copy of the runtime's code in its final binary image. This is problematic because certain global variables used/provided internally by the runtime are duplicated.

This is likely to result in code that doesn't work correctly, for example:

  • memory allocated in one library, and freed in the other would leak or even corrupt the heap.
  • exceptions raised in libfoo.so cannot be caught in libbar.so (and may simply crash the program).
  • the buffering of std::cout not working properly

This problem also happens if you want to link an executable and a shared library to the same static library.

In other words, if your project requires several shared library modules, then use the shared library variant of your C++ runtime.

参考链接:NDK Programmer's Guide

Java泛型:类型擦除

类型擦除


Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass(); 
System.out.println(c1 == c2);

/* Output
true
*/

显然在平时使用中,ArrayList<Integer>()new ArrayList<String>()是完全不同的类型,但是在这里,程序却的的确确会输出true

这就是Java泛型的类型擦除造成的,因为不管是ArrayList<Integer>()还是new ArrayList<String>(),都在编译器被编译器擦除成了ArrayList。 那编译器为什么要做这件事?原因也和大多数的Java让人不爽的点一样——兼容性。由于泛型并不是从Java诞生就存在的一个特性,而是等到SE5才被加 入的,所以为了兼容之前并未使用泛型的类库和代码,不得不让编译器擦除掉代码中有关于泛型类型信息的部分,这样最后生成出来的代码其实是『泛型无关』的, 我们使用别人的代码或者类库时也就不需要关心对方代码是否已经『泛化』,反之亦然。

在编译器层面做的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人非常难受的缺点:

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

List<Integer> list = new ArrayList<Integer>();
Map<Integer, String> map = new HashMap<Integer, String>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

/* Output
[E]
[K, V]
*/

关于getTypeParameters()的解释:

Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.

我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。

public class Main<T> {

    public T[] makeArray() {
        // error: Type parameter 'T' cannot be instantiated directly
        return new T[5];
    }
}

我们无法在泛型内部创建一个T类型的数组,原因也和之前一样,T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。

同时,面对泛型内部形如T var;的代码时,记得多念几遍:它只是个Object,它只是个Object……

public class Main<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        m.set("findingsea");
        String s = m.get();
        System.out.println(s);
    }
}

/* Output
findingsea
*/

虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。

代码片段四展现就是编译器确保了我们放在t上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:

  • set()处的类型检验
  • get()处的类型转换

这两步工作也成为边界动作

public class Main<T> {

    public List<T> fillList(T t, int size) {
        List<T> list = new ArrayList<T>();
        for (int i = 0; i < size; i++) {
            list.add(t);
        }
        return list;
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        List<String> list = m.fillList("findingsea", 5);
        System.out.println(list.toString());
    }
}

/* Output
[findingsea, findingsea, findingsea, findingsea, findingsea]
*/

代码片段五同样展示的是泛型的内部一致性。

擦除的补偿


如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。

public class Main<T> { 
    public T create(Class<T> type) { 
        try { 
            return type.newInstance(); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
        return null; 
    } 
    public static void main(String[] args) { 
        Main<String> m = new Main<String>(); 
        String s = m.create(String.class); 
    } 
}

代码片段六展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。

进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。

public class Main<T> { 
    public T[] create(Class<T> type) { 
        return (T[]) Array.newInstance(type, 10); 
    } 
    public static void main(String[] args) {
        Main<String> m = new Main<String>(); 
        String[] strings = m.create(String.class); 
    } 
}

代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。

参考链接


Java泛型:类型擦除
java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

Android不要使用thread_local定义变量

本来看到Android的ndk都开始用gcc4.8和gcc4.9了,而且gcc4.8.1开始支持全部的c++11的特性,但是Android的run time竟然不支持 thread local storage(TLS),更准确地说,是它没实现。  原来是android的run time没有用gnu的glibc,而是用得Google自己实现的Bionic,很多功能没有实现,留了空接口。 根据维基百科上的说法,Boinic没实现的还不止这个。比glibc而言还不支持的功能还有:

  • 不支持异常处理
  • 无标准模板(这个可以用gnustl或者stlport代替)
  • 不支持宽字符(貌似用处不大,现在主流UTF-8了)
  • 据说它比glibc速度快(也不知道快多少)

目前的解决方案有两个,一个是在TLS的地方排除Android平台,另一个是使用pthread_getspecificpthread_setspecific进行曲线救国。

#if defined(__ANDROID__)
// android 不支持tls
#define THREAD_TLS
#elif defined(__clang__)
#define THREAD_TLS __thread
#elif defined(__cplusplus) && __cplusplus >= 201103L
#define THREAD_TLS thread_local
// VC 2003
#elif defined(_MSC_VER) && (_MSC_VER >= 1300)
#define THREAD_TLS __declspec( thread )
#else
#define THREAD_TLS __thread
#endif

参考链接


OpenMP on Android - TLS workaround
Android NDK undefined reference to ___tls_get_addr

JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xfe

在使用Jni的JNIEnv->NewStringUTF的时候抛出了异常"JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xfe "。网上搜索了一下,这个异常是由于Java虚拟机内部的dalvik/vm/CheckJni.c中的checkUtfString函数抛出的,并且JVM的这个接口明确是不支持四个字节的UTF8字符。因此需要在调用函数之前,对接口传入的字符串进行过滤,过滤函数如下:

int checkUtfString(const char* bytes)
{
    const char* origBytes = bytes;
    if (bytes == NULL) {
        return -1;
    }
    while (*bytes != '\0') {
       unsigned char utf8 = *(bytes++);
        // Switch on the high four bits.
        switch (utf8 >> 4) {
            case 0x00:
            case 0x01:
            case 0x02:
            case 0x03:
            case 0x04:
            case 0x05:
            case 0x06:
            case 0x07: {
                // Bit pattern 0xxx. No need for any extra bytes.
                break;
            }
            case 0x08:
            case 0x09:
            case 0x0a:
            case 0x0b:
            case 0x0f: {
                /*printf("****JNI WARNING: illegal start byte 0x%x\n", utf8);*/
                return -1;
            }
            case 0x0e: {
                // Bit pattern 1110, so there are two additional bytes.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    /*printf("****JNI WARNING: illegal continuation byte 0x%x\n", utf8);*/
                    return -1;
                }
                // Fall through to take care of the final byte.
            }
            case 0x0c:
            case 0x0d: {
                // Bit pattern 110x, so there is one additional byte.
                utf8 = *(bytes++);
                if ((utf8 & 0xc0) != 0x80) {
                    /*printf("****JNI WARNING: illegal continuation byte 0x%x\n", utf8);*/
                    return -1;
                }
                break;
            }
        }
    }
    return 0;
}

参考链接 android jni中 utf-8的check

使用 Mockito 单元测试

目录


1. 需求知识

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

2.2. 存根(Stub) vs. 模拟对象 (Mock)

2.3. 行为测试 vs. 状态测试

2.4. 生成模拟对象

3. 模拟框架( Mock Framework)

4. Mockito

4.1. 使用 Mockito 模拟对象

4.2. 使用 Mockito

4.3. Mockito的限制

4.4. 模拟对象的配置

4.5. 验证模拟对象的行为

4.6. Spy

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

5.2. 安装

6. 链接和参考

1.需求知识

该教程需要理解单元测试和熟悉JUnit框架的使用。

如果您不熟悉JUnit,请阅读JUnit教程。

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

一个单元测试需要在隔离的环境下执行。如果可以的话需要消除其他依赖的服务影响。但实际上,软件中是充满依赖关系的.我们会基于service类写操作类,而service类又是基于数据访问类(DAOs)的,依次下去.

为了解决这个问题, 可以使用 存根 (Stub) 或者 模拟 (Mock) 对象的方法进行测试。

2.2. 存根(Stub) vs. 模拟对象 (Mock)

存根(Stub)类是实现了一个接口或者抽象类的类,可以在测试过程中使用该类,例如:

public class TestStub {
	static interface USB {
		void work();
	}

	static class Mp3Stub implements USB {
		@Override
		public void work() {
			// code.

		}
	 }

	static class Mp4Stub implements USB {
		@Override
		public void work() {
			// code.
		}
	}
}

一个模拟对象(mock object)是一个接口或者抽象类的虚拟实现。例如:

public class TestMock {
	static interface USB {
		void work();
	}

	@Test
	public void testMockObject() {
		USB usb = Mockito.mock( USB.class );
		usb.work();
	}
}

存根和模拟对象都可以传递给其他的对象进行测试。你的一些单元测试可以测这些类的正确性等。利用存根对象或者模拟对象可以保证测试过程中不受到其他的影响。

存根对象需要自定义实现方法;

模拟对象只需要更少的代码和简单的配置。

以下的内容将详细介绍模拟对象的使用方法。

2.3. 行为测试 vs. 状态测试

Mock 对象允许你对行为进行测试。有一些测试不需要验证结果,但是需要检查某些方法是否被正确的参数调用过。这种测试为行为测试。

状态测试只是关注与结果是否正确,而行为测试能够判断一个应用调用结构以及层次。

2.4. 生成模拟对象

你们可以使用Mock 框架来生成模拟对象。Mock 框架允许你在运行期间创建对象,并且定义它的一些行为。

一个典型的例子就是使用模拟对象来模拟数据库DAO层。在生产环境上是使用运行的数据库,但是在单元测试环境中完全可以用模拟对象来模拟数据,确保单元测试的正确条件。这样就不需要依赖于外部的数据。

3. 模拟框架( Mock Framework)

比较流行的模拟框架有 EasyMock、jMock 和 Mockito。下面的列表是这些框架的链接。
# jMock
http://jmock.org/
# EasyMock
http://easymock.org/
# Mockito
http://mockito.org/

4. Mockito

4.1. 使用 Mockito 模拟对象

Mockito 是比较流行的模拟框架,可以与JUnit 联合起来测试。它允许你进行创建和配置模拟对象。

Mockito的官方网站: Mockito 主页.

4.2. 使用 Mockito

Mockito 支持使用 mock() 静态方法创建模拟对象。

同样也支持 @Mock注解方式,如果使用注解的方式,需要使用在初始化方法调用 MockitoAnnotation.InitMock( this ) 方法

例如,下面的例子就是使用 Mockito 进行对类 ClassToTest 的单元测试。

public class MockitoTest {
	@Mock
	MyDatabase databaseMock;

	@Before
	public void setUp() throws Exception {
		MockitoAnnotations.initMocks(this);
	}


	@Test
	public void testQuery() {
		// 需要测试的类
		ClassToTest t = new ClassToTest(databaseMock);
		// 调用方法
		boolean check = t.query("* from t");
		// 验证结果
		assertTrue(check);
		// 模拟对象是否调用了该方法
		Mockito.verify( databaseMock ).query("* from t");
	}
}

提示

可以使用静态导入方法调用方法 mock()

4.3. Mockito的限制

Mockito 以下的类型不能进行构造:

  • 终态类(final classes)
  • 匿名类(anonymous classes)
  • 基本数据类型(primitive types)

4.4. 模拟对象的配置

Mockito 可以使用 verify() 方法来确认某些方法是否被调用过.

when(....).thenReturn(....) 结构可以为某些条件给定一个预期的返回值.

@Test
public void testList() {
	List mock = Mockito.mock( List.class );
	Mockito.when( mock.get( 0 ) ).thenReturn( 1 );
	assertEquals( "预期返回1", 1, mock.get( 0 ) );
}

同样可以使用doReturn(object).when(kdskfsk).methodCall 结构

4.5. 验证模拟对象的行为

Mockito 跟踪了所有的方法调用和参数的调用情况。verify()可以验证方法的行为。

查看下面的例子:

@Test
public void testMap() {
	Map mock = Mockito.mock( Map.class );
	Mockito.when( mock.get( "city" ) ).thenReturn( "深圳" );
	// test code
	assertEquals( "城市测试", "深圳", mock.get( "city" ) );
	Mockito.verify(mock).get( Matchers.eq( "city" ) );
	Mockito.verify( mock, Mockito.times( 2 ) );
}

4.6. Spy

@Spy 或者方法 spy() 可以包含一个真实的对象. 每次调用,除非特出指定,委托给改真实对象的调用.

@Test
public void testSpy() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	list.add( "yes" );
	List spy = Mockito.spy(list);
	//You have to use doReturn() for stubbing
	assertEquals( "yes", spy.get( 0 ) );
	Mockito.doReturn("foo").when(spy).get(0);
	assertEquals( "foo", spy.get( 0 ) );
 }


@Test( expected = IndexOutOfBoundsException.class)
public void testSpy2() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	List spy = Mockito.spy(list);
	// this would not work
	// real method is called so spy.get(0)
	// throws IndexOutOfBoundsException (list is still empty)
	Mockito.when(spy.get(0)).thenReturn("foo");
	assertEquals( "foo", spy.get( 0 ) );
}

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

Mockito 同样也可以在安卓平台上进行测试。

5.2. 安装

在 Android 测试项目中使用 Mockito。添加下面的包到Android 测试项目的 libs 目录
https://mockito.googlecode.com/files/mockito-all-1.9.5.jar
http://dexmaker.googlecode.com/files/dexmaker-1.0.jar
http://dexmaker.googlecode.com/files/dexmaker-mockito-1.0.jar
接下来可以在你的测试项目中使用 Mockito 。

6. 链接和参考

Mockito 项目主页
Mockito 的依赖注入功能
Unit tests with Mockito - Tutorial
使用 Mockito 单元测试 – 教程