Android事件的downTime和eventbus 收不到事件Time有何区别

博客分类:
之前遇到的问题:向系统发送一个长按触摸事件,再发送其他触摸事件时之前的长按事件消失。不多说,直接上代码,用到了测试类Instrumentation,可在上层直接执行,但仅限于当前程序!
package com.zfibs.
import android.app.I
import android.os.SystemC
import android.util.L
import android.view.MotionE
import android.view.MotionEvent.PointerC
import android.view.MotionEvent.PointerP
public class Test {
//向系统注入两点触摸事件
private String TAG = "__Test__";
PointerProperties[] properties = new PointerProperties[2];
PointerProperties pp1 = new PointerProperties();
PointerProperties pp2 = new PointerProperties();
PointerCoords[] pointerCoords = new PointerCoords[2];
public void handleTouch(String args[]) throws Exception {
String[] mArgs =
String opt = mArgs[0];
if (opt.equals("onetouch")) {
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
float x = Float.valueOf(mArgs[1]);
float y = Float.valueOf(mArgs[2]);
int type = Integer.valueOf(mArgs[mArgs.length - 1]);
PointerProperties pp1 = new PointerProperties();
pp1.id = 0;
pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[0] = pp1;
PointerCoords pc1 = new PointerCoords();
pc1.pressure = 1;
pc1.size = 1;
pointerCoords[0] = pc1;
Log.i(TAG, "__one PointTouch__" + x + "___" + y + "___");
MotionEvent event =
if(type == 0){
event = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_DOWN, 1, properties, pointerCoords,
0, 0, 1, 1, 0, 0, 0, 0);
event = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_UP, 1, properties, pointerCoords,
0, 0, 1, 1, 0, 0, 0, 0);
sendPointerTest(event);
} else if (opt.equals("twotouch")) {
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
float x = Float.valueOf(mArgs[1]);
float y = Float.valueOf(mArgs[2]);
int type = Integer.valueOf(mArgs[mArgs.length - 1]);
PointerProperties pp2 = new PointerProperties();
pp2.id = 1;
pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[1] = pp2;
PointerCoords pc2 = new PointerCoords();
pc2.pressure = 1;
pc2.size = 1;
pointerCoords[1] = pc2;
Log.i(TAG, "__two PointTouch__" + x + "___" + y + "___");
MotionEvent event =
if (type == 0) {
event = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_POINTER_2_DOWN, 2, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
event = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_POINTER_2_UP, 2, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
sendPointerTest(event);
} catch (RuntimeException e) {
e.printStackTrace();
public static void sendPointerTest(MotionEvent event) {
Instrumentation inst = new Instrumentation();
inst.sendPointerSync(event);
} catch (Exception e) {
e.printStackTrace();
如果要跨进程发送模拟事件,需调用:
private static void sendPointerSync(MotionEvent event) {
(IWindowManager.Stub.asInterface(ServiceManager
.getService("window"))).injectPointerEvent(event, true);
} catch (RemoteException e) {
e.printStackTrace();
需在源码中编译生成jar包,root权限下降jar导入/system/framework/下,方可调用。
浏览: 907870 次
来自: 深圳
你那个Jni库是自己编译的还是有现成的呢?
一个带人脸识别的智能照相机demohttp://blog.cs ...
咋用啊,搞不懂
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'android区别J2ME - ITeye问答
小弟新手,想请问下J2ME与android都可以开发手机应该,他们的区别和相同点到底在哪里?谢谢
采纳的答案
J2ME与Android比较 - [手机开发]
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
/logs/.html
一、 程序入口
J2me程序入口类为MIDlet,所有的j2me程序都要继承该类来初始化j2me程序。Android程序入口是Activity类。下面先看看它们的生命周期。
J2me MIDlet生命周期
1、 startApp (活动状态)程序启动的时候会调用该方法来初始化用户需要的资源。
2、 PauseApp (暂停状态)当手机来电接电话的时候,会调用该接口。用户可以调用该接口停止也写线程的操作。
3、 destroyApp (销毁状态)程序退出的时候调用。用户可以调用该接口处理处于活动状态的线程。
该几个周期状态有AMS调用转换,也可以程序代码调用转换。resumeRequest调用会的结果是AMS会调用startApp方法,notifyPaused调用的结果是可能AMS调用PsuseApp,notifyDestroyed调用会使AMS销毁该midlet。
Android Activity生命周期
1、 onCreate 程序开始初始化的时候调用该接口,用户导航返回到Activity的时候也会调用该接口。类似于J2me里面多个Canvase(Displayable)切换。所以Activity也类似于Displayable。
2、 onStart onCreate调用完后,程序调用该接口。
3、 onResume onStart调用后调用该接口。此时Activity进入运行状态。
4、 onPause 新的Activity启动的时候调用该接口。
5、 onStop 该Activity 不可见的时候调用。
6、 onDestroy 程序销毁的时候调用。
二、显示组件
J2me 中所有可显示的组件都是直接或间接的继承了Displayable,直接的是Canvas和Screen。不同的继承导致了低级UI和高级UI的区别。 J2me中现成的UI组件都是直接或者间接继承了Screen。只要调用Display.getDisplay(MIDLet instan).setCurrrent(Displayable disp),就可以把组件显示到手机界面上。切换界面的时候也可以使用该接口。
Android 可见的组件直接或者间接继承了android.view.View。通过Activity.setContentView(View view)就可以显示在android手机界面上,切换界面的时候也可以使用该接口。如果是直接继承了View而不是Android自带的UI组件,那么还要自己去实现它的刷新,类似J2me的低级UI组件。
三、UI
J2me高级UI组件由组件内部刷新实现,低级UI可以通过 Canvas的repain()来刷新。低级UI架构可以用MVC方式来实现,建议使用二级缓存。Android提供接口onLayout来提供该 View调整其里面组件元素的位置布局,用户只需要重写该接口就ok。刷新接口onDraw,该接口用来刷新界面里面的绘制元素。该接口类似j2me Canvas的paint接口。不过两个平台传得参数有些意思。J2me传的时Graphics,而android传得是Canvas。Android绘制的时候会传入一个参数Paint。该对象表示绘制的风格,比如颜色,字体大小,字体格式等。如果是移植的话,建议android也使用二级缓存,这样也容易管理。
如果去读API,我们可以发现J2ME中Canvas的repaint()与Android中View的 invalidate()/postInvalidate()方法实现了相同的功能,但是invalidate()/postInvalidate()两者却有着区别:invalidate()只能在UI这个线程里通过调用onDraw(Canvas canvas)来update屏幕显示,而postInvalidate()是要在non-UI线程里做同样的事情的。这就要求我们做判断,哪个调用是本线程的,哪个不是,这在做多线程callback的时候尤为重要。而在J2ME中,不管怎样直接调用repaint()就好了。
另外,Android的View类的显示很大程度上是从XML中读取的,包括了它的layout与很多属性,至于怎么读的以后再谈。而Canvas只需要 Custom Draw就可以了,而且Canvas不能设置大小(那个fullScreenMode免谈),但是View可以。
View 的构造函数,我们不需要再Activity中调用,但是Displayable的是必须的。在Activity中,我们要通过findViewById来从XML中取得View,然后强制转换成View的子类型即可,而J2ME的Canvas是一定要构造出来的。
也许在UI的应用上,Android比J2ME强大的地方,就在于它的View有很多定义好的子类能让我们调用,很方便,也很漂亮。因为Android开源,将来普及之后一定会有很多第三方的控件能为我们所用,这才是大大拓展了Android之处。这会把我们从那些graphics.drawXXX函数中解放出来,也能让我们的应用程序变得更酷更炫。
四、用户事件处理
在J2ME下,Canvas可以响应按键事件与触摸屏事件,它封装了六个 protected的方法,响应六种不同的事件:keyPressed(int keyCode)用于响应按键压下、keyReleased(int keyCode)用于响应按键释放、keyRepeated(int keyCode)用于响应按键长时间压下不释放;pointerDragged(int x,int y)用于响应触摸屏拖拽、pointerPressed(int x,int y)用于响应触摸屏点击、pointerReleased(int x,int y)用于响应触摸屏释放。其中参数方面,keyCode告诉我们哪个按键触发的事件,x、y分别告诉我们触摸屏被点击的坐标(绝对位置)。
在 Android下,View同样可以响应以上两种事件,分别有:boolean onKeyDown(int keyCode,KeyEvent event)用于响应按键点击、boolean onKeyMultiple(int keyCode, int repeatCount,KeyEvent event)用于响应按键重复点击、boolean onKeyUp(int keyCode,KeyEvent event)用于响应按键释放以及onTouchEvent(MotionEvent event)用于响应触摸屏事件。官方API指出onKeyMultiple方法总是返回false的,即它没有handle,因此必须重写才能实现。
在键盘事件方面,J2ME与Android的区别在于Android中定义了KeyEvent这个类,用于描述按键事件。这个KeyEvent可不简单,它能够将一个按键事件描述的淋漓尽致。它的getAction()方法,可以得到按键的行为(down、up or multiple);它的getDownTime()可以得到最近一次keyDown事件发生的时间;它的getEventTime()可以得到本次事件发生的时间;它的getRepeatCount() 可以得到同一按键被连续点击的次数(这个很大程度上是为onKeyMultiple方法设计的)。有一点是非常需要注意的,Android底层在触发 keyDown事件时,有一点与J2ME很不一样:比如我们按下一个键但不释放,J2ME只触发一次keyDown事件由keyPressed()执行,然后就交给keyRepeated()处理,然而Android是每隔一段时间(几十毫秒)就触发一次,onKeyDown方法会连续响应事件,造成意想不到的事情。想解决这个问题倒也不难,你可以通过getAction判断这次事件是否是keyDown,如果是的话,用getEventTime()减去 getDownTime(),如果这个数值太小,可以选择不响应。这个方法简单,但是如果用户输入太快,真正的输入也可能被忽略掉,所以还有另外一种方法:维护一个堆栈,在重写onKeyDown()与onKeyUp()方法的时候,如果getAction()是keyDown,就入栈,如果是 keyUp,就出栈,如果得到一个事件,当它是keyDown的时候,如果当前栈顶是keyDown,就选择性不响应这个事件,这样的话,长点击就相当于一次点击。其实有一些view的子类,比如Button加入了onLongClick()的处理方法这样即使你用选中这个view,长时间按下选择键,也可以相应的处理。
在触摸屏方面,Android只有onTouchEvent()来处理,但是由于它的参数中有 MotionEvent,所以J2ME下分开的三种事件可以通过MotionEvent的getAction()方法加以区分。比较有意思的是 MotionEvent中有一个getPressure()方法,能够得到点击的压力,看来Android手机的元件精密程度很高,乃至软件可以得到压力的大小并通过它来做一些逻辑。
还有一点,Android的KeyEvent与MotionEvent是可以自己构造的,KeyEvent可以通过dispatch()方法将自己传递给KeyEvent的Callback,即事件响应处理方法,这样就能让我们做一个软键盘出来,也可以做许多别的事情。
五、数据库
它们的区别在于android的sqlite很容易建立表到表之前的关联,而J2ME必须实现自己的一套框架,而且ANDROID的sqlite提供一些接口(如:SQLiteOpenHelper),数据库开发会很容易, J2ME可以看成在一个文件读写一些序列化的东西,而android只要会一些sql语句。
已解决问题
未解决问题今天看啥 热点:
Robotium源码分析之Instrumentation进阶,robotium
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架。鉴于之前本人已经转载和编写了Instrumentation的一些文章,所以建议大家如果没有看过的还是翻看下先对Instrumentation有个基本的理解。然后带着疑问再来看这篇文章看是否能帮上自己。
既然是分析Instrumentation,那么我们必须要先看下Instrumentation 这个类的类图,直接网上截获,就不花时间另外去画了,但请注意网上该图是比较老的,一些新的注入事件的方法是没有加进去的,注意红色部分:
开始分析之前我们要搞清楚Instrumentation的几点
1. Instrumentation测试脚本和目标app在同一个进程中运行
如官方描述的“instrumentation can load both a test package and the application under test into the same process. Since the application components and their tests are in the same process, the tests can invoke methods in the components, and modify and examine fields
in the components.“翻译过来就是“Instrumentation可以把测试包和目标测试应用加载到同一个进程中运行。既然各个控件和测试代码都运行在同一个进程中了,测试代码当然就可以调用这些控件的方法了,同时修改和验证这些控件的一些项也不在话下了。“
这个从ddms的线程输出可以证明,通过&am instrumentation -w com.example.android.notepad/android.test.InstrumentationTestRunner& 运行的输出如下:
我们对比不是通过&am instrumentation&命令启动的notepad应用的线程输出:
可以看到是没有“Instr:android.test.InstrumentationTestRunner这个线程的。这个线程和Instrumentation又是什么关系呢?
*/ public class InstrumentationTestRunner
extends Instrumentation
implements TestSuiteProvider
}从它的类定义我们可以看到它是从我们的Instrumentation类继承下来的。其实从它的名字我们就大概可以想像到它是扮演什么角色的,参照我们之前对UiAutomator的源码分析《UIAutomator源码分析之启动和运行》,InstrumentationTestRunner扮演的角色类似于当中的UiAutomatorTestRunner类,都是通过解析获取和建立目标测试用例和测试集然后知道测试的运行。
大家先看下它的onCreate方法的官方定义:“Called when the instrumentation is starting, before any application code has been loaded.“。翻译过来就是”这个方法是在Instrumentation启动过程中,在目标测试代码被装载进内存运行之前进行调用“
我们可以看下这个方法的实现,看它是如何启动刚才的那个InstrumetnationTestRunner线程的:
public void onCreate(Bundle arguments)
super.onCreate(arguments);
TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(), getTargetContext().getClassLoader());
if (testSizePredicate != null) {
testSuiteBuilder.addRequirements(new Predicate[] { testSizePredicate });
if (testAnnotationPredicate != null) {
testSuiteBuilder.addRequirements(new Predicate[] { testAnnotationPredicate });
if (testNotAnnotationPredicate != null) {
testSuiteBuilder.addRequirements(new Predicate[] { testNotAnnotationPredicate });
if (testClassesArg == null) {
parseTestClasses(testClassesArg, testSuiteBuilder);
testSuiteBuilder.addRequirements(getBuilderRequirements());
this.mTestRunner = getAndroidTestRunner();
this.mTestRunner.setContext(getTargetContext());
this.mTestRunner.setInstrumentation(this);
this.mTestRunner.setSkipExecution(logOnly);
this.mTestRunner.setTest(testSuiteBuilder.build());
this.mTestCount = this.mTestRunner.getTestCases().size();
if (this.mSuiteAssignmentMode) {
this.mTestRunner.addTestListener(new SuiteAssignmentPrinter());
WatcherResultPrinter resultPrinter = new WatcherResultPrinter(this.mTestCount);
this.mTestRunner.addTestListener(new TestPrinter(&TestRunner&, false));
this.mTestRunner.addTestListener(resultPrinter);
this.mTestRunner.setPerformanceResultsWriter(resultPrinter);
}从中我们可以看到这个方法开始就是如上面所说的类似UiAutomatorTestRunner一样去获取解析对应测试包里面的测试集和测试用例,这个在这个章节不是重点,重点是最后面的start()这个方法的调用。这个方法最终调用的是父类Instrumentation的start()方法,我们看下这个方法的官方解析&Create and start a new thread in which to run instrumentation.“翻译过来就是”创建一个新的运行Instrumentation(测试用例)的线程&:
public void start()
if (this.mRunner != null) {
throw new RuntimeException(&Instrumentation already started&);
this.mRunner = new InstrumentationThread(&Instr: & + getClass().getName());
this.mRunner.start();
}在第125行我们很明显知道新的线程名就叫做&Instr:android.test.InstrumentationTestRunner&,因为这个方法是从子类android.test.InstrumentationTestRunner中传进来的,所以getClass().getName()方法获得的就是子类的名字。
我们继续看这个线程是如何建立起来的,继续进入InstrumentationThread这个Instrumentation的内部类:
private final class InstrumentationThread
extends Thread {
/* 1689 */
public InstrumentationThread(String name) { super(); }
public void run() {
/* 1693 */
Process.setThreadPriority(-8);
} catch (RuntimeException e) {
/* 1695 */
Log.w(&Instrumentation&, &Exception setting priority of instrumentation thread & + Process.myTid(), e);
/* 1698 */
if (Instrumentation.this.mAutomaticPerformanceSnapshots) {
/* 1699 */
Instrumentation.this.startPerformanceSnapshot();
/* 1701 */
Instrumentation.this.onStart();
从最前面的定义可看到该类是继承与Thread对象的,所以后面提供个重写的run方法来代表该线程的运行入口完成一个Thread线程的完整实现。
1701行,Instrumentation.this值得就是子类InstrumentationTestRunner的实例,那么它的onStart方法又做了什么事情呢?
* Initialize the current thread as a looper.
* Exposed for unit testing.
void prepareLooper() {
Looper.prepare();
public void onStart() {
prepareLooper();
if (mJustCount) {
mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
finish(Activity.RESULT_OK, mResults);
if (mDebug) {
Debug.waitForDebugger();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream writer = new PrintStream(byteArrayOutputStream);
StringResultPrinter resultPrinter = new StringResultPrinter(writer);
mTestRunner.addTestListener(resultPrinter);
long startTime = System.currentTimeMillis();
mTestRunner.runTest();
long runTime = System.currentTimeMillis() - startT
resultPrinter.printResult(mTestRunner.getTestResult(), runTime);
} catch (Throwable t) {
// catch all exceptions so a more verbose error message can be outputted
writer.println(String.format(&Test run aborted due to unexpected exception: %s&,
t.getMessage()));
t.printStackTrace(writer);
} finally {
mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
String.format(&\nTest results for %s=%s&,
mTestRunner.getTestClassName(),
byteArrayOutputStream.toString()));
if (mCoverage) {
generateCoverageReport();
writer.close();
finish(Activity.RESULT_OK, mResults);
}该方法一开始就为InstrumentationTestRunner线程建立一个looper消息队列,至于looper是怎么回事,大家如果不清的请查看网络的解析。Looper是用于给一个线程添加一个消息队列(MessageQueue),并且循环等待,当有消息时会唤起线程来处理消息的一个工具,直到线程结束为止。通常情况下不会用到Looper,因为对于Activity,Service等系统组件,Frameworks已经为我们初始化好了线程(俗称的UI线程或主线程),在其内含有一个Looper,和由Looper创建的消息队列,所以主线程会一直运行,处理用户事件,直到某些事件(BACK)退出。
如果,我们需要新建一个线程,并且这个线程要能够循环处理其他线程发来的消息事件,或者需要长期与其他线程进行复杂的交互,这时就需要用到Looper来给线程建立消息队列。
建立好消息队列后往下的重点就是调用AndroidTestRunner的runTest方法开始测试用例的执行了:
public void runTest(TestResult testResult) {
mTestResult = testR
for (TestListener testListener : mTestListeners) {
mTestResult.addListener(testListener);
Context testContext = mInstrumentation == null ? mContext : mInstrumentation.getContext();
for (TestCase testCase : mTestCases) {
setContextIfAndroidTestCase(testCase, mContext, testContext);
setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
setPerformanceWriterIfPerformanceCollectorTestCase(testCase, mPerfWriter);
testCase.run(mTestResult);
}大概做法就是对所有收集到的测试集进行一个for循环然后取出每个测试用例在junit.Framework.Testcase环境下进行运行了。这里就不往下研究junit框架是怎么回事了。
总结以上的分析,android.test.InstrumentationTestRunner会在目标应用代码运行之前调用onCreate方法来建立一个新的线程并准备后消息队列,然后会开始基于Instrumentation的测试集的测试。
2. runOnUiThread和runOnMainSync的区别
既然app的主线程和instrumetnaiton测试用例脚本线程是运行在同一个进程中的,我们脑袋中应该就会立刻闪现以下有关UiThread和子线程的两点限制:
子线程是可以直接获取主线程UiThread的控件以及内容的子线程是不能直接操作主线程UiThread的控件以及内容的
根据网上的文章《Android中UI线程与后台线程交互设计的5种方法》的描述,android提供了以下几种方法,用于实现后台线程与UI线程的交互。
1、handler2、Activity.runOnUIThread(Runnable)3、View.Post(Runnable)4、View.PostDelayed(Runnabe,long)5、AsyncTask
而Instrumentation类又提供了一个runOnMainSync的方法,这和上面的Activity提供的runOnUiThread方法从名字上比较接近,那么我们这里就对比下这两种方法有什么区别。
我们先看下Activity的runOnUIThread方法:
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
* @param action the action to run on the UI thread
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
action.run();
}其代码的功能和对应的描述一致:
如果这个方法不是在运行Activity的主线程UiThread上被调用的,也就是在子线程上调用的,那么把action提交到主线程的Main Looper消息队列中排队然后返回如果这个方法是在运行Activity的主线程UiThread上被调用的,那么不需要进入Main Looper队列排队,直接调用执行
你看,这多偏心,正室就是权力大,二房就是差点,特别是二房比较多的时候,你慢慢排吧。不过我们的Main Looper还算好了,在我们现实中更多是反过来了,搞了个二房三房后把正室给休掉了。
这里就不往外扯了,这里我们主要关心子线程的情况,既然是把action提交到Main Looper进行排队,那么必然是异步的了,否则消息队列的存在就没有意思了,所以这里我们要在脑袋总记得runOnUiThread是异步的。
我们再看Instrumentation的runOnMainSync方法:
public void runOnMainSync(Runnable runner)
validateNotAppThread();
SyncRunnable sr = new SyncRunnable(runner);
this.mThread.getHandler().post(sr);
sr.waitForComplete();
}这里也是从再从主线程获得Main Looper的Handler后往Main Looper消息队列中提交action,但人家提交完之后还会等待该action线程的执行完毕才会退出这个函数,所以两个方法的区别就是:Activity的runOnUiThread是异步执行的,Instrumentation的runOnMainSync是同步执行的。runOnMainSync又是怎么实现这一点的呢?这个我们就要看Instrumetnation的内部类SyncRunnable了:
private static final class SyncRunnable implements Runnable {
private final Runnable mT
private boolean mC
/* 1715 */
public SyncRunnable(Runnable target) { this.mTarget = }
public void run()
/* 1719 */
this.mTarget.run();
/* 1720 */
synchronized (this) {
/* 1721 */
this.mComplete =
/* 1722 */
notifyAll();
public void waitForComplete() {
/* 1727 */
synchronized (this) {
/* 1728 */
while (!this.mComplete) {
/* 1730 */
catch (InterruptedException e) {}
}它也是从runnable线程类继承下来的。在run方法的行我们可以看到,该运行在Main UiThread的方法在跑完后会把Instrumentation实例的mComplete变量设置成true,而runOnMainSync最后调用的运行在子线程中的waitForComplete方法会一直等待这个mComplete变量变成true才会返回,也就是说一直等待主线程的调用完成才会返回,那么到了这里就很清楚runOnMainSync是如何通过SyncRunnable这个内部类实现同步的了。
这里还有必要提一提的是runOnMainSync方法调用的第一个函数validateNotAppThread,其实它做的事情就是去查看下当前线程的Looper是不是主线程的Main Looper,如果是的话就直接抛出异常,代表runOnMainSynce这个方法是不应该在主线程调用的;如果不是的话就什么都不干继续往下跑。
private final void validateNotAppThread()
/* 1650 */
if (Looper.myLooper() == Looper.getMainLooper()) {
/* 1651 */
throw new RuntimeException(&This method can not be called from the main application thread&);
3. Instrumentation注入事件统一方式-- InputManager
从开始的类图,我们可以看到Instrumentation事件相关的方法如下:
Description
Key Events
sendKeySync
发送一个键盘事件,注意同一时间只有一个action,或者是按下,或者是弹起,所有下面其他key相关的事件注入都是以这个方法为基础的
sendKeyDownUpSync
基于sendKeySync发送一个按键的按下和弹起两个事件
sendCharacterSync
发送键盘上的一个字符,完整的过程包括一个按下和弹起事件
sendStringSync
往应用发送一串字符串
Tackball Event
sendTrackballEventSync
发送轨迹球事件。个人没有用过,应该是像黑莓的那种轨迹球吧
Pointer Event
sendPointerSync
发送点击事件
那么我们根据不同的事件类型看下它们注入事件的方式是如何的,我们先看按键事件类型,因为其他的按键事件都是最终调用sendKeySync,所以我们就看这方法就可以了:
* Send a key event to the currently focused window/view and wait for it to
* be processed.
Finished at some point after the recipient has returned
* from its event processing, though it may &em&not&/em& have completely
* finished reacting from the event -- for example, if it needs to update
* its display as a result, it may still be in the process of doing that.
* @param event The event to send to the current focus.
public void sendKeySync(KeyEvent event) {
validateNotAppThread();
long downTime = event.getDownTime();
long eventTime = event.getEventTime();
int action = event.getAction();
int code = event.getKeyCode();
int repeatCount = event.getRepeatCount();
int metaState = event.getMetaState();
int deviceId = event.getDeviceId();
int scancode = event.getScanCode();
int source = event.getSource();
int flags = event.getFlags();
if (source == InputDevice.SOURCE_UNKNOWN) {
source = InputDevice.SOURCE_KEYBOARD;
if (eventTime == 0) {
eventTime = SystemClock.uptimeMillis();
if (downTime == 0) {
downTime = eventT
KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,
deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source);
InputManager.getInstance().injectInputEvent(newEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}这个就很明显了,用的就是InputManager的事件注入方式,如果大家不清楚的请查看本人之前翻译的《Monkey源码分析番外篇之Android注入事件的三种方法比较》。
下一个我们就看下轨迹球相关事件注入,用到的同样是InputManager的事件注入方式:
* Dispatch a trackball event. Finished at some point after the recipient has
* returned from its event processing, though it may &em&not&/em& have
* completely finished reacting from the event -- for example, if it needs
* to update its display as a result, it may still be in the process of
* doing that.
* @param event A motion event describing the trackball action.
(As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
public void sendTrackballEventSync(MotionEvent event) {
validateNotAppThread();
if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}最后我们看下点击事件,同样,使用的也是无一例外的InputManager的事件注入方式:
* Dispatch a pointer event. Finished at some point after the recipient has
* returned from its event processing, though it may &em&not&/em& have
* completely finished reacting from the event -- for example, if it needs
* to update its display as a result, it may still be in the process of
* doing that.
* @param event A motion event describing the pointer action.
(As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
public void sendPointerSync(MotionEvent event) {
validateNotAppThread();
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
4. 文本输入的两种方式
从上面两节我们可以看到,在Instrumetnaiton框架中,我们操作目标应用控件的方式比如输入文本有两种方式,这些都可以从本文最后的实例中看到:
通过runOnMainSync调用直接把文本修改的动作运行在UiThread这个主线程中通过注入事件模拟用户通过按键输入字符
那么这两种方式有什么区别呢:
runOnMainSync: 直接在主线程中修改控件的文本,所以不需要通过键盘驱动,也就是说不需要调出任何的键盘。这样的好处是效率以及不需要担心中英文输入的问题事件注入方式:模拟用户的输入,所以肯定会调出键盘,这样在中文等非默认英文输入的情况下容易碰到问题,毕竟中文字串也是通过拼音组合而成,那么拼音出来后选择哪个出来的组合就成问题了。比如输入&changan&可能出来的是&长安“,”长按“等组合,那么哪个是我们想要的呢?
5. 跨进程和安全问题
众所周知Instrumentation和基于Instrumentation的Robotium对跨进程跨应用的支持是不支持的(其实Robotium从android 4.3之后开始支持UiAutomation框架,理应可以支持跨应用的,这个往后文章我们会进行分析).
但是从上面第3节的分析我们看到它使用的是InputManager的事件注入方式,大家翻回本人之前的文章:《monkey源码分析之事件注入方法变化》,MonkeyRunner通过Monkey注入事件使用的也是InputManager方式啊。那么为什么基于Monkey的MonkeyRunner就能跨进程跨应用,基于Instrumentation的Robotium就不能跨应用呢?我个人认为有以下几点原因:
首先,一个应用要使用Instrumentation进行测试的话首先必须要在其Manifest.xml做相应的配置,那么一个应用真正发布的时候肯定是把这些配置给去掉的,所以Instrumentation或基于Instrumentation的Robotium肯定是不能对其他应用进行操作的,不然它就可以随意的打开一个流量消耗大户应用来消耗你的流量了。其次,既然大家里面都用了InputManager进行事件注入,那么为什么Monkey可以跨应用而Robotium不行呢?你Robotium也可以绕开Instrumentation框架直接调用InputManager来做事情啊!这里就要说到INJECT_EVENTS这个系统权限了,大家请参考《Monkey源码分析番外篇之Android注入事件的三种方法比较》。人家Monkey是google亲生的,获取个INJECT_EVENTS系统权限还不容易吗,你Robotium跟我什么关系,我google凭什么给你这些第三方应用开放这个权限呢?鬼知道给你开放这个权限后会不会搞破坏啊!所以你还是待在配置了Mainifest.xml的你的目标测试应用中做事情吧,别到处跑了
6.所谓钩子
都说Android instrumentation是Android系统里面的一套控制方法或者”钩子“。这些钩子可以在正常的生命周期(正常是由操作系统控制的)之外控制Android控件的运行,其实指的就是Instrumentation类提供的各种流程控制方法,下表展示了部分方法的对应关系
Control by User(Instrumentation)
Control by OS
callActivityOnCreate
callActivityOnDestroy
callActivityOnStart
默认来说,一个Activity的创建和消亡都是由操作系统来控制调用的,用户是没办法控制的。比如用户是没法直接调用onCreate方法来在activity启动的时候做一些初始化动作。但是Instrumentation提供了对应的callActivityOnCreate方法来允许用户控制对onCreate方法的调用,所以这里本来属于操作系统的控制权就移交给用户了。
* Perform calling of an activity's {@link Activity#onCreate}
The default implementation simply calls through to that method.
* @param activity The activity being created.
* @param icicle The previously frozen state (or null) to pass through to
onCreate().
public void callActivityOnCreate(Activity activity, Bundle icicle) {
activity.performCreate(icicle);
}从代码可以看到它做的事情也就是直接调用Activity类的performCreate方法:
final void performCreate(Bundle icicle) {
onCreate(icicle);
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
而performCreate方法最终调用的就是onCreate方法。注意performCreate这个方法是属于Internal API,它不是public出去给外部使用的.
所以这里就好比Instrumentation勾住了本应该系统调用的onCreate方法,然后由用户自己来控制勾住的这个方法什么时候执行。
7. Instrumentation跨应用的考虑
安卓从Android4.3开始引进了UiAutomation框架来支持通过Accessibility API来实现针对用户界面UI层面的功能测试,Instrumentation也提供了相应的接口来获得UiAutomation实例:
* Gets the {@link UiAutomation} instance.
* &strong&Note:&/strong& The APIs exposed via the returned {@link UiAutomation}
* work across application boundaries while the APIs exposed by the instrumentation
* do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
* not allow you to inject the event in an app different from the instrumentation
* target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
* will work regardless of the current application.
* A typical test case should be using either the {@link UiAutomation} or
* {@link Instrumentation} APIs. Using both APIs at the same time is not
* a mistake by itself but a client has to be aware of the APIs limitations.
* @return The UI automation instance.
* @see UiAutomation
public UiAutomation getUiAutomation() {
if (mUiAutomationConnection != null) {
if (mUiAutomation == null) {
mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
mUiAutomationConnection);
mUiAutomation.connect();
return mUiA
}关于UiAutomation更多的描述请查看本人上一个系列关于UiAutomator源码分析的文章,这里列出来方便大家浏览:
《Android4.3引入的UiAutomation新框架官方简介》《UIAutomator源码分析之启动和运行》《UiAutomator源码分析之UiAutomatorBridge框架》《UiAutomator源码分析之注入事件》《UiAutomator源码分析之获取控件信息》
从上面的一系列文章可以看到UiAutomator运用UiAutomation框架进行UI自动化测试是做了很多工作,进行了很多高层的封装来方便用户使用的。而Robotium仅仅是引入了获取UiAutomation的实例这个api来暴露给用户使用,一个方面,当然没有高层的封装提供了很多自由,但是也是这些自由让你想快速开发脚本无所适从!Robotium现阶段(5.2.1)对比UiAutomator或者Appium在使用UiAutomation来测试UI就好比,Robotium相当于一个原始社会的人自由的披着件兽皮两手空空的在原始森林中自由游猎,碰到猛兽可以自由的选择工具随意组装来进行猎杀,但很有可能工具没有组装好怪兽却先把你给吃了;UiAutomator相当于一个现代的人全副武装AK47的在原始森林根据GPS定位如履薄冰的向目标猎物靠近来猎杀猎物。两者都是使用最终由化学元素组成的工具来猎杀猎物,但早已高层封装好的ak47和你临时抱佛脚去凭空组建个弓弩从效率上又怎么能比呢。
所以这里我怀疑Robotium可能就提供这个接口就算了,不会再做上层的封装,因为UiAutomator已经做了,人家UiAutomator是google自家的,什么时候又改动人家最清楚,你怎么跟得住人家呢?况且它从Instrumentation的不可跨进程到提供了一个跨进程的突破口,也给了确实需要跨进程调用的用户的一个突破口,不提供太多的封装还能美其名曰“自由”了。注意,仅仅是我自己的猜想了,如果Robotium往后真对UiAutomation做高成封装的话就当我发神经得了。
8.Instrumentation使用例子
* Copyright (C) 2008 The Android Open Source Project
* Licensed under the Apache License, Version 2.0 (the &License&);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an &AS IS& BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
package come.example.android.notepad.
import android.test.ActivityInstrumentationTestCase2;
import com.example.android.notepad.NotesL
import com.example.android.notepad.NoteE
import com.example.android.notepad.NotesL
import com.example.android.notepad.R;
import android.app.A
import android.app.I
import android.app.Instrumentation.ActivityM
import android.content.I
import android.os.SystemC
import android.test.InstrumentationTestC
import android.view.KeyE
import android.widget.TextV
* Make sure that the main launcher activity opens up properly, which will be
* verified by {@link #testActivityTestCaseSetUpProperly}.
public class NotePadTest extends ActivityInstrumentationTestCase2&NotesList& {
NotesList mActivity =
* Creates an {@link ActivityInstrumentationTestCase2} for the {@link NotesList} activity.
public NotePadTest() {
super(NotesList.class);
//private static Instrumentation instrumentation = new Instrumentation();
protected void setUp() throws Exception {
super.setUp();
//Start the NotesList activity by instrument
Intent intent = new Intent();
intent.setClassName(&com.example.android.notepad&, NotesList.class.getName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Instrumentation inst = getInstrumentation();
mActivity = (NotesList) inst.startActivitySync(intent);
protected void tearDown()
mActivity.finish();
super.tearDown();
} catch (Exception e) {
e.printStackTrace();
* Verifies that the activity under test can be launched.
public void testActivityTestCaseSetUpProperly() {
assertNotNull(&activity should be launched successfully&, getActivity());
public void testActivity() throws Exception {
//Add activity monitor to check whether the NoteEditor activity's ready
ActivityMonitor am = getInstrumentation().addMonitor(NoteEditor.class.getName(), null, false);
//Evoke the system menu and press on the menu entry &Add note&;
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().invokeMenuActionSync(mActivity, R.id.menu_add, 0);
//Direct to the NoteEditor activity
Activity noteEditorActivity = getInstrumentation().waitForMonitorWithTimeout(am, 60000);
assertEquals(NoteEditor.class,noteEditorActivity.getClass());
SystemClock.sleep(3000);
//assertEquals(true, getInstrumentation().checkMonitorHit(am, 1));
TextView noteEditor = (TextView) noteEditorActivity.findViewById(R.id.note);
//Get the text directly, DON'T need to runOnMainSync at all!!!
String text = noteEditor.getText().toString();
assertEquals(text,&&);
//runOnMainSync to change the text
getInstrumentation().runOnMainSync(new PerformSetText(noteEditor,&Note1&));
//inject events to change the text
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_1);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_2);
getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_P);
getInstrumentation().sendStringSync(&gotohell&);
//getInstrumentation().callActivityOnPause(noteEditorActivity);
Thread.sleep(5000);
//getInstrumentation().callActivityOnResume(noteEditorActivity);
//Save the new created note
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().invokeMenuActionSync(noteEditorActivity, R.id.menu_save, 0);
private class PerformSetText implements Runnable {
public PerformSetText(TextView t,String text) {
public void run() {
tv.setText(txt);
相关搜索:
相关阅读:
相关频道:
Android教程最近更新}

我要回帖

更多关于 eventbus 粘性事件 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信