做站要先做人,总会得到回报的

mikel阅读(1152)

对于体验站来说,推广是摆在每个站长面前的一件头等大事儿,没有注册量就意味着没有任务转化率,也就没有收入,盈利模式就是赚联盟的任务差价,没有别的收入渠道,所以大家都很专注在推广这一块,自从2013年以来互联网的体验站如雨后春笋般茁壮成长起来,不单单是各个老站把握着市场,因为作为体验站来说的留存率很低,回头率基本上都是一时的,有高额的赏金任务就会有一批人来做,赚一把就走了。

所以说做体验站大部分都在拼转化率,也就是任务的转化率,往往做任务的就那么多人,来源也基本上固定在网赚论坛等等站点,推广不管是悬赏注册激励还是奖励都是吸引眼球的事儿,很多新站不被人重视,就是因为怕新站提现支付押款,很多人辛苦打码或者做任务赚了几块钱,你还拖着不支付给他,让人很心寒,还有就是推广你的站注册了几个人,结果提现不及时,伤了推广员的心,网站运营首先要建立基本的信任,不要拖欠提现的钱,大家都不容易草根何必为难草根儿呢!

运营网站最关键的一点就是要持之以恒,信任也不是一天建立起来的,我的站建了3年了,至今盈利不多,主要原因还是自己懒,懒得推广,但是坚持不押款及时支付,珍惜每个注册的用户和帮忙推广的人,每次提现都表达感谢,并建群加他们及时对网站的新功能和任务进行沟通反馈,努力做到让这些人成为忠实的用户,树立口碑,最近注册量上来了,通过网站统计多数是因为各位网赚站长的推荐给力,很多都是多次推广提现的老推广员的站了,真的非常感谢他们!做站要先做人,总会得到回报的!

 

没人认识的“大白”(●—●)

mikel阅读(1191)

清明小长假,带着女儿爬山逛夜市,看到有人在卖大白(●—●)玩偶,女儿看到了就不肯挪步了,看过《超能特工队》的女儿和我都非常喜欢,于是给她买了一个,女儿爱不释手,抱着继续逛街,一路上很多人看到她怀里的大白,有人认识,有人不认识。认识的总会怀着喜欢的喊一声:大白(●—●)!

路过地摊有些摊主看到女儿那么喜欢的样子,好奇的问这个玩偶是啥?女儿有点儿自豪的回答:大白!

那些没看过电影的人很不解这么一个看着样子挺怪的玩偶有什么可爱之处让她爱不释手。

女儿对这些不认识的人感到很奇怪,电影里那么可爱的大白,他们居然认为不可爱,还觉得样子怪怪的,我跟她解释说:这些人没看过电影,所以不认识大白很正常,也就不知道大白多可爱了。她觉得有道理的点点头。

不由得想到互联网企业的产品是不是很多时候也遭遇这种尴尬,小众人群的产品,小众里面的人爱不释手,大众不知所谓甚至不理解这些人为啥要用这么个产品,一个产品从诞生到成为大众品牌得到大众的认可看来要经历的过程不可谓不漫长,一部电影造就了一个角色,但是要想广而告之那就不是只能靠电影能够人尽皆知了。可见想要将产品深入人心有多难!很佩服可口可乐、小米那些公司的品牌塑造到人的心智里,一个名字就知道其价值。

54fd0c783dfae942d4000005

不积跬步无以至千里

mikel阅读(1253)

很多人都在找好的网络营销方法,各个听到有人发布新的营销方法,就蜂拥而至纷纷效仿,然后看不到效果就扭头去找其他的方法,其实这种人只能说是在想着一夜暴富做互联网,完全是赌徒心理,没有将互联网项目当成事业在经营,其实互联网上很多事儿需要路遥知马力的,根本不是那些骗子鼓吹的日赚多少等等。

今天看到有人分享的《巧借另类视频营销,自动获得大流量》的方法,看了下作者的确用心,深挖了视频营销的定位和主题选择,还有植入广告的技巧,但是重要的是作者说道的:

“这样短短的小视频,也就是三分钟,上传轻松简单,审核通过率高。普及开来,每天上传十个,坚持三个月,你可以上传900个视频。到时候,流量会超出你的想象。”

可见如果没有大毅力很难看到效果,这就是要互联网项目的操作的关键点要持之以恒,另外要勇于尝试,做才有机会,不做永远就是想象。

 

[转载]游标不包括正在修改的表,或该表不能通过此游标更新 - xiaoli_zhou2011的日志 - 网易博客

mikel阅读(1584)

[转载]游标不包括正在修改的表,或该表不能通过此游标更新 – xiaoli_zhou2011的日志 – 网易博客.

(asp)连接SQL2005时,SQL数据库名字不能改,改过以后就会报这样的错

错误一:
[Microsoft][ODBC SQL Server Driver][SQL Server]在 sys.servers 中找不到服务器 ‘anywolfs.com’。请验证指定的服务器名称是否正确。如果需要,请执行存储过程 sp_addlinkedserver 以将服务器添加到 sys.servers。

错误二:
[Microsoft][ODBC SQL Server Driver][SQL Server]未将服务器 ‘anywolfs.com’ 配置为用于 DATA ACCESS。

错误三:
[Microsoft][ODBC SQL Server Driver][SQL Server]此游标不包括正在修改的表,或该表不能通过此游标更新。

在asp中如果我使用rs.add和rs.update来添加数据就会报错误三,如果我使用insert into,就不会发生任何错误。在网上搜索半天,发现解决方法挺好用,方法如下:

解决:

如果是直接更换的服务器,那么主机名更换了,以前存在数据库的主机名也需要删除。进入SQL2005查询器
执行:

select @@servername
EXEC sp_dropserver @@servername
EXEC sp_addserver [new_name], local

第一行是查出老的实例名
第二行是删除老的实例名
第三行是 ‘new_name’ 是新的实例名,也就是当前计算机名,在重启一下sql2005服务,

执行完这一步,这时候如果asp会报错:

Microsoft OLE DB Provider for ODBC Drivers 错误 ‘80004005’
[Microsoft][ODBC SQL Server Driver][SQL Server]未将服务器 ‘anywolfs.com’ 配置为用于 DATA ACCESS。

需要设置模式,执行:
EXEC sp_serveroption ‘anywolfs.com’, ‘data access’, ‘true’

这时如果asp遇到:

[Microsoft][ODBC SQL Server Driver][SQL Server]此游标不包括正在修改的表,或该表不能通过此游标更新。
则需要再重启sql2005,

重 启后还是没有解决问题,那么可以使用sp_helpserver 查看有多少实例名,然后统统都给他删掉(sp_dropserver @@服务器名    来删除所有服务器),在执行 sp_addserver 等其他操作。或者重新运行一下上面的命令,重新做一遍,重启sql2005解决不了问题可以重启服务器看看。

我遇到的是错误3

先查询出来再全都删除,在创建

 

[转载]在sysservers 中找不到服务器 "名字 " 执行 sp_addlinkedserver 将该服务器添加到 “名字” - keilsi的专栏 - 博客频道 - CSDN.NET

mikel阅读(1165)

[转载]在sysservers 中找不到服务器 “名字 ” 执行 sp_addlinkedserver 将该服务器添加到 “名字” – keilsi的专栏 – 博客频道 – CSDN.NET.

Microsoft OLE DB Provider for SQL Server 错误 ‘80004005’Microsoft OLE DB Provider for SQL Server 错误 ‘80004005’

在 sysservers 中找不到服务器 ‘WebServer’。请执行 sp_addlinkedserver 将该服务器添加到 sysservers。

SQL2005当用记录集删除时出现问题,增加修改记录都没问题
SQL服务器的问题,准确地说,是改了你的数据库的主机名导致SQL链接服务器没有随着更新,你可以新建一个查询,输入select * from master..sysservers,查询SQL链接服务器,看一下srvname是什么,与主机名相同吗?

所以,你得更新链接服务器,删除以前的那个链接服务器,或者把主机名改回来
登录到服务器 ,在查询窗口中执行如下两条命令 :
1,sp_dropserver ‘oldComputerName’
2,sp_addserver ‘newComputerName’,local
3重新启动服务就可以继续进行发布的创建了

[转载]Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条) - Mobile Internet developer - 博客频道 - CSDN.NET

mikel阅读(976)

[转载]Android 高手进阶之自定义View,自定义属性(带进度的圆形进度条) – Mobile Internet developer – 博客频道 – CSDN.NET.

转载请注明地址:http://blog.csdn.net/xiaanming/article/details/10298163

很 多的时候,系统自带的View满足不了我们功能的需求,那么我们就需要自己来自定义一个能满足我们需求的View,自定义View我们需要先继承 View,添加类的构造方法,重写父类View的一些方法,例如onDraw,为了我们自定义的View在一个项目中能够重用,有时候我们需要自定义其属 性,举个很简单的例子,我在项目中的多个界面使用我自定义的View,每个界面该自定义View的颜色都不相同,这时候如果没有自定义属性,那我们是不是 需要构建不同颜色的View出来呢,这样子我们的代码就会显得很沉厄,所以这时候我们就需要自定义其属性来满足我们不同的需求,自定义属性呢,我们需要在 values下建立attrs.xml文件,在其中定义我们需要定义的属性,然后在自定义View中也要做相对应的修改,我们还是用一个小例子来看看自定 义View和自定义属性的使用

 

今天带大家来自己定义一个带进度的圆形进度条,我们还是先看一下效果吧

从上面可以看出,我们可以自定义圆环的颜色,圆环进度的颜色,是否显示进度的百分比,进度百分比的颜色,以及进度是实心还是空心等等,这样子是不是很多元化很方便呢?接下来我们就来教大家怎么来定义

 

1.在values下面新建一个attrs.xml,现在里面定义我们的属性,不同的属性对应不同的format,属性对应的format可以参考http://blog.csdn.net/pgalxx/article/details/6766677,介绍的还是比较详细,接下来我贴上我在自定义这个进度条所用到的属性

<!--?xml version="1.0" encoding="UTF-8"?-->















2.自定义View的属性我们算是定义好了,接下来就是怎么获取属性和代码的编写了,我们需要在构造方法中获取我们自己定义的相关属性,我们先调用context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar)来获取TypedArray,然后从TypedArray获取我们定义的属性,例如

roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);
roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN);
textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15);
roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);
max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);
style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

上面的代码中,如roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED); getColor方法的第一个参数是我们在XML文件中定义的颜色,如果我们没有给我们自定义的View定义颜色,他就会使用第二个参数中的默认值,即Color.RED

3.为了方便大家理解,我将自定义View的全部代码贴出来,里面的代码我也有详细的注释

package com.example.roundprogressbar;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.example.circlepregress.R;

/**
* 仿iphone带进度的进度条,线程安全的View,可直接在线程中更新进度
* @author xiaanming
*
*/
public class RoundProgressBar extends View {
/**
* 画笔对象的引用
*/
private Paint paint;

/**
* 圆环的颜色
*/
private int roundColor;

/**
* 圆环进度的颜色
*/
private int roundProgressColor;

/**
* 中间进度百分比的字符串的颜色
*/
private int textColor;

/**
* 中间进度百分比的字符串的字体
*/
private float textSize;

/**
* 圆环的宽度
*/
private float roundWidth;

/**
* 最大进度
*/
private int max;

/**
* 当前进度
*/
private int progress;
/**
* 是否显示中间的进度
*/
private boolean textIsDisplayable;

/**
* 进度的风格,实心或者空心
*/
private int style;

public static final int STROKE = 0;
public static final int FILL = 1;

public RoundProgressBar(Context context) {
this(context, null);
}

public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

paint = new Paint();

TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
R.styleable.RoundProgressBar);

//获取自定义属性和默认值
roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);
roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN);
textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15);
roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);
max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);
style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

mTypedArray.recycle();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

/**
* 画最外层的大圆环
*/
int centre = getWidth()/2; //获取圆心的x坐标
int radius = (int) (centre - roundWidth/2); //圆环的半径
paint.setColor(roundColor); //设置圆环的颜色
paint.setStyle(Paint.Style.STROKE); //设置空心
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
paint.setAntiAlias(true); //消除锯齿
canvas.drawCircle(centre, centre, radius, paint); //画出圆环

Log.e("log", centre + "");

/**
* 画进度百分比
*/
paint.setStrokeWidth(0);
paint.setColor(textColor);
paint.setTextSize(textSize);
paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
int percent = (int)(((float)progress / (float)max) * 100); //中间的进度百分比,先转换成float在进行除法运算,不然都为0
float textWidth = paint.measureText(percent + "%"); //测量字体宽度,我们需要根据字体的宽度设置在圆环中间

if(textIsDisplayable &amp;&amp; percent != 0 &amp;&amp; style == STROKE){
canvas.drawText(percent + "%", centre - textWidth / 2, centre + textSize/2, paint); //画出进度百分比
}

/**
* 画圆弧 ,画圆环的进度
*/

//设置进度是实心还是空心
paint.setStrokeWidth(roundWidth); //设置圆环的宽度
paint.setColor(roundProgressColor); //设置进度的颜色
RectF oval = new RectF(centre - radius, centre - radius, centre
+ radius, centre + radius); //用于定义的圆弧的形状和大小的界限

switch (style) {
case STROKE:{
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(oval, 0, 360 * progress / max, false, paint); //根据进度画圆弧
break;
}
case FILL:{
paint.setStyle(Paint.Style.FILL_AND_STROKE);
if(progress !=0)
canvas.drawArc(oval, 0, 360 * progress / max, true, paint); //根据进度画圆弧
break;
}
}

}

public synchronized int getMax() {
return max;
}

/**
* 设置进度的最大值
* @param max
*/
public synchronized void setMax(int max) {
if(max &lt; 0){
throw new IllegalArgumentException("max not less than 0");
}
this.max = max;
}

/**
* 获取进度.需要同步
* @return
*/
public synchronized int getProgress() {
return progress;
}

/**
* 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
* 刷新界面调用postInvalidate()能在非UI线程刷新
* @param progress
*/
public synchronized void setProgress(int progress) {
if(progress &lt; 0){ throw new IllegalArgumentException("progress not less than 0"); } if(progress &gt; max){
progress = max;
}
if(progress &lt;= max){
this.progress = progress;
postInvalidate();
}

}

public int getCricleColor() {
return roundColor;
}

public void setCricleColor(int cricleColor) {
this.roundColor = cricleColor;
}

public int getCricleProgressColor() {
return roundProgressColor;
}

public void setCricleProgressColor(int cricleProgressColor) {
this.roundProgressColor = cricleProgressColor;
}

public int getTextColor() {
return textColor;
}

public void setTextColor(int textColor) {
this.textColor = textColor;
}

public float getTextSize() {
return textSize;
}

public void setTextSize(float textSize) {
this.textSize = textSize;
}

public float getRoundWidth() {
return roundWidth;
}

public void setRoundWidth(float roundWidth) {
this.roundWidth = roundWidth;
}

}

4.通过上面几步我们就实现了自定义View,和自定义View的属性,当然使用过程中还是有一点变化,我们必须在界面布局的最顶层加上

xmlns:Android_custom=”http://schemas.Android.com/apk/res/com.example.circlepregress”这个即命名空间,

红色部分是自定义属性的前缀,什么意思呢?对于Android系统控件我们定义其控件属性是用android:XXX=”XXXXXXX”,而我们自己定义的就用android_custom:XXX = “XXXXXX”
绿色部分则是我们的包的名字

通过上面这两步我们就能自己定义属性了,我贴出自定义View在XML中使用情况


今天就到此结束,如果大家有什么疑问,请留言,我会及时回复大家的

 

项目源码,点击下载

 

[转载]Android动画原理分析-android-爱编程

mikel阅读(936)

[转载]Android动画原理分析-android-爱编程.

最近在Android上做了一些动画效果,网上查了一些资料,有各种各样的使用方式,于是乘热打铁,想具体分析一下动画是如何实现的,Animation, Animator都有哪些区别等等。

首先说AnimationAndroid.view.animation.Animation)对象。

无论是用纯java代码构建Animation对象,还是通过xml文件定义Animation,其实最终的结果都是

Animation a = new AlphaAnimation();

Animation b = new ScaleAnimation();

Animation c = new RotateAnimation();

Animation d = new TranslateAnimation();

分别是透明度,缩放,旋转,位移四种动画效果。

而我们使用的时候,一般是用这样的形式:

View.startAnimation(a);

那么就来看看View中的startAnimation()方法。

1.View.startAnimation(Animation)

先是调用View.setAnimation(Animation)方法给自己设置一个Animation对象,这个对象是View类中的一个名为mCurrentAnimation的成员变量。

然后它调用invalidate()来重绘自己。

我想,既然setAnimation()了,那么它要用的时候,肯定要getAnimation(),找到这个方法在哪里调用就好了。于是通过搜索,在View.draw(Canvas, ViewGroup, long)方法中发现了它的调用,代码片段如下:

2.View.draw(Canvas, ViewGroup, long)

其中调用了View.drawAnimation()方法。

3.View.drawAnimation(ViewGroup, long, Animation, boolean)

代码片段如下:

其中调用了Animation.getTransformation()方法。

4.Animation.getTransformation(long, Transformation, float)

该方法直接调用了两个参数Animation.getTransformation()方法。

5.Animation.getTransformation(long, Transformation)

该方法先将参数currentTime处理成一个float表示当前动画进度,比如说,一个2000ms的动画,已经执行了1000ms了,那么进度就是0.5或者说50%。

然后将进度值传入插值器(Interpolator)得到新的进度值,前者是均匀的,随着时间是一个直线的线性关系,而通过插值器计算后得到的是一个曲线的关系。

然后将新的进度值和Transformation对象传入applyTranformation()方法中。

6.Animation.applyTransformation(float, Transformation)

Animation的applyTransformation()方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。分别看下四个子类的applyTransformation()的实现。

ScaleAnimation

AlphaAnimation

RotateAnimation

TranslateAnimation

可见applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下60FPS,是一个非常流畅的画面了,也就是16ms,为了验证这一点,我在applyTransformation方法中加入计算时间间隔并打印的代码进行验证,代码如下:

最终得到的log如下图所示:

右侧是“手动”计算出来的时间差,有一定的波动,但大致上是16-17ms的样子,左侧是日志打印的时间,时间非常规则的相差20ms。

于是,根据以上的结果,可以得出以下内容:

1.首先证明了一点,Animation.applyTransformation()方法,是动画具体的调用方法,我们可以覆写这个方法,快速的制作自己的动画。

2.另一点,为什么是16ms左右调用这个方法呢?是谁来控制这个频率的?

对于以上的疑问,我有两个猜测:

1.系统自己以postDelayed(this, 16)的形式调用的这个方法。具体的写法,请参考《使用线程实现视图平滑滚动》

2.系统一个死循环疯狂的调用,运行一系列方法走到这个位置的间隔刚好是16ms左右,如果主线程卡了,这个间隔就变长了。

为了找到答案,我在Stack Overflow上发帖问了下,然后得到一个情报,那就是让我去看看Choreographer(android.view.Choreographer)类。

1.Choreographer的构造方法

看了下Choreographer类的构造方法,是private的,不允许new外部类new,于是又发现了它有一个静态的getInstance()方法,那么,我需要找到getInstance()方法被谁调用了,就可以知道Choreographer对象在什么地方被使用。一查,发现Choreographer.getInstance()在ViewRootImpl的构造方法中被调用。以下代码是ViewRootImpl.ViewRootImpl(Context, Display)的片段。

OK,找到了ViewRootImpl中拥有一个mChoreographer对象,接下来,我需要去找,它如何被使用了,调用了它的哪些方法。于是发现如下代码:

scheduleTraversals()方法中,发现了这个对象的使用。

2.Choreographer.postCallback(int, Runnable, Object)

该方法辗转调用了两个内部方法,最终是调用了Choreographer.postCallbackDelayedInternal()方法。

3.Choreographer.postCallbackDelayedInternal(int, Object, Object, long)

这个方法中,

1.首先拿到当前的时间。

这里参数中有一个delay,它的值可以具体查看一下,你会发现它就是一个静态常量,定义在Choreographer类中,它的值是10ms。也就是说,理想情况下,所有的时间都是以100FPS来运行的。

2.将要执行的内容加入到一个mCallbackQueues中。

3.然后执行scheduleFrameLocked()或者发送一个Message。

接着我们看Choreographer.scheduleFrameLocked(long)方法

4.Choreographer.scheduleFrameLocked(long)

if判断进去的部分是是否使用垂直同步,暂时不考虑。

else进去的部分,还是将消息发送到mHandler对象中。那我们就直接来看mHandler对象就好了

5.Choreographer.FrameHandler

mHandler实例的类型是FrameHandler,它的定义就在Choreographer类中,代码如下:

它的处理方法中有三个分支,但最终都会调用这个doFrame()方法。

6.Choreographer.doFrame()

doFrame()方法巴拉巴拉一大段,但在下面有非常工整的一段代码,一下就吸引了我的眼球。

它调用了三次doCallbacks()方法,暂且不说这个方法是干什么的,但从它的第一个参数可以看到分别是输入(INPUT),动画(ANIMATION),遍历(TRAVERSAL)

于是,我先是看了下这三个常量的意义。下图所示:

显然,注释是说:输入事件最先处理,然后处理动画,最后才处理view的布局和绘制。接下来我们看看Choreographer.doCallbacks()里面做了什么。

7.Choreographer.doCallbacks(int, long)

这个方法的操作非常统一,有三种不同类型的操作(输入,动画,遍历),但在这里却看不见这些具体事件的痕迹,这里我们不得不分析一下mCallbackQueues这个成员变量了。

mCallbackQueues是一个CallbackQueue对象数组。而它的下标,其意义并不是指元素1,元素2,元素3……而是指类型,请看上面doCallbacks()的代码,参数callbackType传给了mCallbackQueues[callbackType]中,而callbackType是什么呢?

其实就是前面说到的三个常量,CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_TRAVERVAL

那么只需要根据不同的callbackType,就可以从这个数组里面取出不同类型的CallbackQueue对象来。

那么CallbackQueue又是什么呢?

CallbackQueueChoreographer的一个内部类,其中我认为有两个很重要的方法,分别是:extractDueCallbacksLocked(long)addCallbackLocked(long, Object, Object)

先说addCallbackLocked(long, Object, Object)

1.CallbackQueue.addCallbackLocked(long, Object, Object)

首先它通过一个内部方法构建了一个CallbackRecord对象,然后后面的if判断和while循环,大致上是将参数中的对象链接在CallbackRecord的尾部。其实CallbackRecord就是一个链表结构的对象。

2.CallbackQueue.extractDueCallbacksLocked(long)

这个方法是根据当前的时间,选出执行链表中与该时间最近的一个操作来处理,实际上,我们可以通俗的理解为“跳帧”。

想象一下,如果主线程运行的非常快速,非常流畅,每一步都能在10ms内准时运行到,那么我们的执行链表中的元素始终只有一个。

如果主线程中做了耗时操作,那么各种事件一直在往各自的链表中添加,但是当主线程有空来执行的时候,发现链表已经那么多积累的过期的事件了,那么就直接选择最后一个来执行,那么界面上看起来,就是卡顿了一下。

到这里为止,我们得出以下结论:

1.控制外部输入事件处理,动画执行,UI变化都是在同一个类中做的处理,即是Choreographer,其中它规定的了理想的运行间隔为10ms,因为各种操作需要花费一定的时间,所以外部执行的间隔统计出来是大约16ms。

2.在Choreographer对象中有三条链表,分别保存着待处理的输入事件,待处理的动画事件,待处理的遍历事件。

3.每次执行的时候,Choreographer会根据当前的时间,只处理事件链表中最后一个事件,当有耗时操作在主线程时,事件不能及时执行,就会出现所谓的“跳帧”,“卡顿”现象。

4.Choreographer的共有方法postCallback(callbackType, Object)是往事件链表中放事件的方法。而doFrame()是消耗这些事件的方法。

事到如今,已经探究出不少有用的细节。这里,又给自己提出一个问题,根据以上的事实,那么,只需要找到哪些东西再往这三条链表中放事件呢?

于是进一步探究一下。我们只需要找到postCallback()被哪些方法调用了即可。

于是请点击这里,通过grepcode列举了调用postCallback()的方法。

搞明白了Choreographer的工作原理,再去看ObjectAnimator,ValueAnimator的实现,就非常的轻松了。

ObjectAnimator.start()方法实际上是辗转几次调用了ValueAnimator的start()方法,ValueAnimator.start()又调用了一个临时变量animationHandler.start()。

animationHandler实际上是一个Runnable,其中start()方法调用了scheduleAnimation()。

而这个方法:

调用了postCallback()方法。

将this(Runnable)post之后,实际上肯定就是要执行Runnable.run()方法

run()方法中又调用了doAnimationFrame()方法。这个方法具体的实现了动画的某一帧的过程,然后再次调用了scheduleAnimation()方法。

就相当于postDelayed(this, 16)这种方式了。

到这里为止,对Animation原理的分析就到此结束了,本来只想分析下Animation的实现过程,没想到顺带研究了一下Choreographer的工作原理,今天收获还是不少。

其实还有好多疑问,技术学习也一天急不得,靠的是每日慢慢的积累,相信总有一天,各种疑惑都会迎刃而解。

[转载]解决IllegalStateException: Can not perform this action after onSaveInstanceState - 第一菜鸟XMU - 博客频道 - CSDN.NET

mikel阅读(1037)

[转载]解决IllegalStateException: Can not perform this action after onSaveInstanceState – 第一菜鸟XMU – 博客频道 – CSDN.NET.

今天使用Fragment的时候,出现了这个错误 IllegalStateException: Can not perform this action after onSaveInstanceState:

  1. E/AndroidRuntime(12747): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  2.     at Android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1314)
  3.     at Android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1325)

 

是在使用FragmentTransition的 commit方法添加一个Fragment的时候出现的,后来在官网找到了相关的

说明:http://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()

public abstract int commitAllowingStateLoss ()                                    Added in API level 11

Like commit() but allows the commit to be executed after an activity’s state is saved. This is dangerous

because the commit can be lost if the activity needs to later be restored from its state, so this should

only be used for cases where it is okay for the UI state to change unexpectedly on the user.

大致意思是说我使用的 commit方法是在Activity的onSaveInstanceState()之后调用的,这样会出错,因为onSaveInstanceState

方法是在该Activity即将被销毁前调用,来保存Activity数据的,如果在保存玩状态后再给它添加Fragment就会出错。解决办法就

是把commit()方法替换成 commitAllowingStateLoss()就行了,其效果是一样的。

[转载]android的窗口机制分析------事件处理 - linghu_java的专栏 - 博客频道 - CSDN.NET

mikel阅读(963)

[转载]android的窗口机制分析——事件处理 – linghu_java的专栏 – 博客频道 – CSDN.NET.

由于Android是linux内核的,所以它的事件处理也在linux的基础上完成的,因此本文我们从linux 内核往应用这个方向慢慢理清它的处理过程。

    linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/路径下创建我们硬件输入设备的节点,一般情况下在我们的 手机中这些节点是以eventXX来命名的,如event0,event1等等,但是如果是虚拟机的话,我们可以看到一个mice,这个mice代表鼠标 设备,这是由于PC需要使用鼠标来模拟触屏。由于这些设备节点是硬件相关的,所以每款设备都是不尽相同的。看到了这些输入的设备节点,我们可能比较困惑这 些eventXX到底代表什么含义呢,也就是说到底是什么样的设备创建了这个节点呢?我们可以从/proc/bus/input/devices中读出 eventXX相关的硬件设备,这里具体的就不多说了,我们只需要知道Android读取事件信息就是从/dev/input/目录下的设备节点中读取出 来的,算是android事件处理的起源吧,可以让大家知道按键、触屏等事件是从哪里来的,不是我们的重点。

    首先,简而言之的介绍一下android事件传递的流程,按键,触屏等事件是经由WindowManagerService获取,并通过共享内存和管道的 方式传递给ViewRoot,ViewRoot再dispatch给Application的View。当有事件从硬件设备输入 时,system_server端在检测到事件发生时,通过管道(pipe)通知ViewRoot事件发生,此时ViewRoot再去的内存中读取这个事 件信息。

    至于android在事件处理上为什么使用共享内存而不是直接使用Binder机制,我的猜测应该是google为了保证事件响应的实时性,因此在选择进 程间传递事件的方式中,选择了高的共享内存的方式,由于共享内存在数据管理过程中基本不涉及到内存的数据拷贝,只是在进程读写时涉及到2次数据拷贝,这个 是不可避免的数据拷贝,因此这种方式能够很好的保证系统对事件的响应,但是仅仅是共享内存是不够的,因为共享内存的通信方式并不能够通知对方有数据更新, 因此android在事件处理过程中加入了另一种进程间通信方式管道(pipe),管道的效率不如共享内存高,会不会影响事件处理的实时性?没关系,每次 system_serve通知ViewRoot只是向其传递一个字符,即轻巧有简单,一个字符的多次数据拷贝,我想google还是能够接受的。

    好的,了解了一些基本知识后,我们从底层往上层来分析事件的传递过程,这里为了下文便于理解,首先列出整个事件处理的结构图。

    

1. 事件处理系统的初始化过程

    前文讲到android的事件处理系统,这里称为事件传递系统更贴切一些,因为android事件系统中比较复杂就是其传递过程,下面我们就以事件传递系 统来代替事件处理系统。android事件传递系统是以共享内存和管道的进程间通信方式来实现传递的,为了便于理解它的传递机制,事件传递系统的初始化工 作的理解则会显得非常的重要。

    1.1 创建管道连接

    事件传递系统中的管道的主要作用是在有事件被存储到共享内存中时,system_server端通知ViewRoot去读取事件的通信机制。既然是 ViewRoot和system_server之间建立管道通信,那么ViewRoot和WindowManagerService(负责事件传递,运行 在system_server进程中)各需维护管道的一个文件描述符,其实ViewRoot和WindowManagerService不是各维护了一个 管道的文件描述符,而是两个,当然了这两个描述符不属于同一管道,实际上也就是ViewRoot和WindowManagerService之间实现了全 双工的管道通信。

    WindowManagerService—>ViewRoot方向的管道通信,表示WMS通知ViewRoot有新事件被写入到共享内存;

    ViewRoot–>WindowManagerService方向的管道通信,表示ViewRoot已经消化完共享内存中的新事件,特此通知WMS。

    ViewRoot和WindowManagerService的管道的文件描述符都是被存储在一个名为InputChannel的类中,这个InputChannel类是管道通信的载体。

    首先来看ViewRoot端的管道的建立。

    setView()@ViewRoot.java

  1. requestLayout();  
  2. mInputChannel = new InputChannel();  
  3. try {  
  4.     res = sWindowSession.add(mWindow, mWindowAttributes,  
  5.             getHostVisibility(), mAttachInfo.mContentInsets,  
  6.             mInputChannel);  
  7. catch (RemoteException e) {  

    在ViewRoot和WMS(WindowManagerService)建立起连接之前首先会创建一个InputChannel对象,同样的WMS 端也会创建一个InputChannel对象,不过WMS的创建过程是在ViewRoot调用add()方法时调用的。InputChannel的构造不 做任何操作,所以在ViewRoot中创建InputChannel时尚未初始化,它的初始化过程是在调用WMS方法add()时进行的,看到上面代码中 将mInputChannel作为参数传递给WMS,目的就是为了初始化。下面转到WMS代码看看InputChannel的初始化过程。

    addWindow()@WindowManagerService.java

  1. if (outInputChannel != null) {  
  2.     String name = win.makeInputChannelName();  
  3.     InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);  
  4.     win.mInputChannel = inputChannels[0];  
  5.     inputChannels[1].transferToBinderOutParameter(outInputChannel);  
  6.       
  7.     mInputManager.registerInputChannel(win.mInputChannel);  
  8. }  

    outInputChannel为ViewRoot传递来的InputChannel对象,上述代码主要的工作其实就是创建一对 InputChannel,这一对InputChannel中实现了一组全双工管道。 在创建InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符。 InputChannel创建完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRoot端InputChannel对象的初始化,这样随着ViewRoot和WMS两端的 InputChannel对象的创建,事件传输系统的管道通信也就建立了起来。

    创建InputChannel pair的过程以及管道建立,共享内存申请的过程就不再列出它的代码了,请参考 openInputChannelPair()@InputTransport.cpp。下图为ViewRoot和WMS两端创建 InputChannel pair之后的结构。

    

    1.2 InputChannel的注册过程

    上一节介绍了InputChannel对象的创建过程,这个过程将管道通信建立了起来,但是我们需要清楚的一点是,一个管道通信只是对应一个 Activity的事件处理,也就是当前系统中有多少个Activity就会有多少个全双工管道,那么系统需要一个管理者来管理以及调度每一个管道通信, 因此我们在创建完InputChannel对象后,需要将其注册到这个管理者中去。

    明白了InputChannel对象需要注册的原因之后,我们再看ViewRoot和WMS端的InputChannel对象各自需要注册到哪里?其实也 很好理解,两个InputChannel对象WMS端的是管道通信的sender, ViewRoot端的是Receiver(尽管创建的全双工,但是目前只使用到了它的一向的通信,另一方向的通信尚未使用),那么着两个 InputChannel对象肯定需要被两个不同的管理者来管理。ViewRoot端的一般情况下会注册到一个NativeInputQueue对象中 (这是一个Native的对象,而JAVA端的InputQueue类仅仅是提供了一些static方法与NativeInputQueue通信),只要 当用到NativeActivity时,会是另外一种处理机制,这里我们不管它,NativeActivity毕竟很少用到;WMS端注册在 InputManager对象中。其实从NativeInputQueue和InputManager的名字中也就能知道各自的功能了。

    1.2.1 注册到NativeInputQueue

    ViewRoot端InputChannel对象在向NativeInputQueue注册时,需要注册3个参数:

    1. 将InputChannel对象对应的Native InputChannel传递给NativeInputQueue;

    2. 将ViewRoot的成员变量InputHandler传递给NativeInputQueue,这个InputHandler则是事件的处理函数,传递它的作用主要是明确当前ViewRoot的事件处理函数;

    3. 还有一个很重要的参数需要传递给NativeInputQueue,那就是当前Application的主进程的MessageQueue。

    其实,android在实现事件传输时,很大程度上借用了线程Looper和MessageQueue的轮询(poll)机制,通过它的轮询机制来检测管 道上是否有消息通知事件发生,借用Looper机制能够很大限度的保证事件能够第一时间被Application知晓, Looper这块会单独分析一下。

    在注册过程中,android会将InputChannel对象中保存的管道的文件描述符交给MessageQueue的native looper去监听,同时向native looper指示一个回调函数,一旦有事件发生,native looper就会检测到管道上的数据,同时会去调用指示的回调函数。这个回调函数为 handleReceiveCallback()@android_view_InputQueue.cpp.

    当然了,NativeInputQueue对象,整个系统中只有这么一个,它为了负责管理这么多的Application的事件传递,android在 NativeInputQueue类中定义了一个子类Connection,每个InputChannel对象在注册时都会创建一个自己的 Connection对象。

    

    这一块的代码在registerInputChannel()@android_view_InputQueue.cpp

    1.2.2 注册到InputManager

    由于WMS端的对linux Input 系统的检测和ViewRoot对管道接收端的检测机制不同,前面分析过了,ViewRoot端很好的复用了Application 主线程的Looper轮询机制来实现对事件响应的实时性,而WMS尽管也有自己的Looper,WMS却没像ViewRoot一样复用自己的Looper 机制,至于原因android的code上没有明确说明,我的猜测应该是WMS是整个系统的,不像ViewRoot一样每个Activity都有一套,为 了不影响系统的整体性能,尽量不要去影响WMS。

    不采用Looper来轮询是否有事件发生,InputManager启动了2个进程来管理事件发生与传递,InputReaderThread和 InputDispatcherThread,InputReaderThread进程负责轮询事件发生; InputDispatcherThread 负责dispatch事件。为什么需要2个进程来管理,用一个会出现什么问题?很明显,如果用一个话,在轮询input系统event的时间间隔会变长, 有可能丢失事件。

    虽然没有使用Looper来轮询事件的发生,但是InputDispatcher使用了native looper来轮询检查管道通信,这个管道通信表示InputQueue是否消化完成dispatch过去的事件。注意的是这个native looper并不是WMS线程的,而是线程InputDispatcher自定定义的,因此所有的轮询过程,需要InputDispatcher主动去调 用,如

     mLooper->pollOnce(timeoutMillis);或者mLooper->wake();。而不像NativeInputQueue一样,完全不用操心对looper的操作。

    WMS在初始化时会创建这么一个InputManager实例,当然了,它也是系统唯一的。JAVA层的InputManager实例并没有实现太多 的业务,真正实现Input Manager业务是Native的NativeInputManager实例,它在被创建时,建立起了整个WMS端事件传递系统的静态逻辑,如下图:

    

    NativeInputManager的整个业务的核心其实是InputReader和InputDispatcher两个模块,下面简单介绍一下这两个模块。

    A. InputReader

    InputReader从名称就可以看出主要任务是读事件,基本上它所有的业务都包含在了process()的函数中,

  1. void InputReader::process(const RawEvent* rawEvent) {  
  2.     switch (rawEvent->type) {  
  3.     case EventHubInterface::DEVICE_ADDED:  
  4.         addDevice(rawEvent->deviceId);  
  5.         break;  
  6.   
  7.     case EventHubInterface::DEVICE_REMOVED:  
  8.         removeDevice(rawEvent->deviceId);  
  9.         break;  
  10.   
  11.     case EventHubInterface::FINISHED_DEVICE_SCAN:  
  12.         handleConfigurationChanged(rawEvent->when);  
  13.         break;  
  14.   
  15.     default:  
  16.         consumeEvent(rawEvent);  
  17.         break;  
  18.     }  
  19. }  

   process()函数的输入参数时EventHub模块提供的,

    1.当EventHub尚未打开input系统eventXX设备时,InputReader去向EventHub获取事件时,EventHub会首先去 打开所有的设备,并将每个设备信息以RawEvent的形式返给InputReader,也就是process()中处理的 EventHubInterface::DEVICE_ADDED类型,该过程会根据每个设备的deviceId去创建InputDevice,并根据设 备的classes来创建对应的InputMapper。如上图所示。

    2.当所有的设备均被打开之后,InputReader去向EventHub获取事件时,EventHub回去轮询event节点,如果有事件,InputReader则会消化该事件consumeEvent(rawEvent);

    B. InputDispatcher

    数据传输管理的核心业务是在InputDispatcher中完成的,因此最终WMS端InputChannel对象会注册到 InputDispatcher中,同样的由于整个系统中InputDispatcher实例只有一个,而WMS端InputChannel对象是和 ViewRoot一一对应的,因此InputDispatcher类中也定义了一个内部类Connect来管理各自的InputChannel对象。不同 于NativeInputQueue类中的Connect类,InputDispatcher中的Connect类的核心业务是由 InputPublisher对象来实现的,该对象负责将发生的事件信息写入到共享内存。
相关代码在registerInputChannel()@InputDispatcher.cpp

2. 事件传递

    经过分析事件处理系统的初始化过程之后,我们已经对事件处理系统的整体架构有了一定程度的理解,那么下面的事件传递过程就会显得很easy了。

    2.1 InputReaderThread线程操作

     当input系统有事件发生时,会被InputReaderThread线程轮询到,InputReader会根据事件的device id来选择的InputDevice,然后再根据事件的类型来选择InputDevice中的InputMapper,InputMapper会将事件信 息通知给InputDispatcher;

    目前adroid在InputReader中实现了5种设备类型的InputMapper,分别为滑盖/翻盖SwitchInputMapper、键盘 KeyboardInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单 点触屏SingleTouchInputMapper。

设备类型 InputManager EventType Notify InputDispatcher
滑盖/翻盖 SwitchInputMapper EV_SW notifySwitch()
键盘 KeyboardInputMapper EV_KEY notifyKey()
轨迹球 TrackballInputMapper EV_KEY, EV_REL,

EV_SYN

notifyMotion()
单点触屏 SingleTouchInputMapper EV_KEY, EV_ABS,

EV_SYN

notifyMotion()
多点触屏 MultiTouchInputMapper EV_ABS,

EV_SYN

notifyMotion()

    其中EV_REL为事件相对坐标,EV_ABS为绝对坐标,EV_SYN表示Motion的一系列动作结束。

    Notify InputDispatcher表示不同的事件通知InputDispatcher的函数调用,这几个函数虽然是被InputReaderThread调用的,单却是在InputDispatcher定义的。

    

    2.1.1 notifySwitch()

  1. void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,  
  2.         uint32_t policyFlags) {  
  3. #if Debug_INBOUND_EVENT_DETAILS  
  4.     LOGD(“notifySwitch – switchCode=%d, switchValue=%d, policyFlags=0x%x”,  
  5.             switchCode, switchValue, policyFlags);  
  6. #endif  
  7.   
  8.     policyFlags |= POLICY_FLAG_TRUSTED;  
  9.     mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);  
  10. }  


    Switch事件的处理是比较简单的,这是一个与Activity无关的事件,因此我们根本不需要将其dispatch到ViewRoot,所以在 notifySwitch()方法中直接通知给PhoneWindowManager去处理即可。从上面的类图中我们其实可以发现mPolicy指向的就 是NativeInputManager,

  1. void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,  
  2.         int32_t switchValue, uint32_t policyFlags) {  
  3. #if Debug_INPUT_DISPATCHER_POLICY  
  4.     LOGD(“notifySwitch – when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x”,  
  5.             when, switchCode, switchValue, policyFlags);  
  6. #endif  
  7.   
  8.     JNIEnv* env = jniEnv();  
  9.   
  10.     switch (switchCode) {  
  11.     case SW_LID:  
  12.         env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged,  
  13.                 when, switchValue == 0);  
  14.         checkAndClearExceptionFromCallback(env, “notifyLidSwitchChanged”);  
  15.         break;  
  16.     }  
  17. }  

    NativeInputManager的notifySwitch()最终会调用到notifySwitch()@PhoneWindowManager.java

    2.1.2 notifyKey()

  1. void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,  
  2.         uint32_t policyFlags, int32_t action, int32_t flags,  
  3.         int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {  
  4. #if Debug_INBOUND_EVENT_DETAILS  
  5.     LOGD(“notifyKey – eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, “  
  6.             “flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld”,  
  7.             eventTime, deviceId, source, policyFlags, action, flags,  
  8.             keyCode, scanCode, metaState, downTime);  
  9. #endif  
  10.     if (! validateKeyEvent(action)) {  
  11.         return;  
  12.     }  
  13.   
  14.     policyFlags |= POLICY_FLAG_TRUSTED;  
  15.     mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,  
  16.             keyCode, scanCode, /*byref*/ policyFlags);  
  17.   
  18.     bool needWake;  
  19.     { // acquire lock  
  20.         AutoMutex _l(mLock);  
  21.   
  22.         int32_t repeatCount = 0;  
  23.         KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,  
  24.                 deviceId, source, policyFlags, action, flags, keyCode, scanCode,  
  25.                 metaState, repeatCount, downTime);  
  26.   
  27.         needWake = enqueueInboundEventLocked(newEntry);  
  28.     } // release lock  
  29.   
  30.     if (needWake) {  
  31.         mLooper->wake();  
  32.     }  
  33. }  

InputDispatcher对KeyBoard事件的处理如上述代码所述,

    首先,InputDispatcher会截取这个按键事件,根据当前设备的状况来优先消化这个事件,这个过程当然是在将事件dispatch给 ViewRoot之前。同样的就像notifySwitch()一样,最终该过程交由 interceptKeyBeforeQueueing()@PhoneWindowManager.java来处理。 interceptKeyBeforeQueueing()主要是对一些特殊案件的特殊处理,并判断该按键是够应该传递给ViewRoot。通过设置标志 位policyFlags的值来判断是否给ViewRoot,例如policyFlags&POLICY_FLAG_PASS_TO_USER == 1 则应该传递给ViewRoot。
    interceptKeyBeforeQueueing()特殊处理主要是针对在锁屏或者屏幕不亮的情况的下收到特殊的键值,如音量键或者wake键。wake键是指能够点亮屏幕的键时的操作。
    其次,InputDispatcher再将该按键信息存储在一个队列中(enqueueInboundEventLocked()@InputDispatcher.cpp)。
  1. Queue<EventEntry> mInboundQueue;  

    2.1.3 notifyMotion()

  1. mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);  

首先,同样的,InputDispatcher会截取这个motion事件,不同的是motion事件的截取处理NativeInputManager完 全有能力处理,所以并没有交给PhoneWindowManager来处理。查看代码 interceptGenericBeforeQueueing()@com_android_server_InputManager.cpp.

    其次,InputDispatcher再将该motion事件信息存储在mInboundQueue队列中(enqueueInboundEventLocked()@InputDispatcher.cpp)。

    2.2 InputDispatcherThread线程操作

    InputDispatcherThread线程的轮询过程 dispatchOnce()–>dispatchOnceInnerLocked(), InputDispatcherThread线程不停 的执行该操作,以达到轮询的目的,我们的研究重点也就放在这2个函数处理上。

    2.2.1 InputDispatcherThread基本流程

    InputDispatcherThread的主要操作是分两块同时进行的,

    一部分是对InputReader传递过来的事件进行dispatch前处理,比如确定focus window,特殊按键处理如HOME/ENDCALL等,在预处理完成 后,InputDispatcher会将事件存储到对应的focus window的outBoundQueue,这个outBoundQueue队列是InputDispatcher::Connection的成员函数, 因此它是和ViewRoot相关的。

    一部分是对looper的轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件,如果NativeInputQueue处理 完成事件,它就会向通过管道向InputDispatcher发送消息指示consume完成,只有NativeInputQueue consume完成一个事件,InputDispatcher才会向共享内存写入另一个事件。

    

    2.2.3 丢弃事件

    并不是所有的InputReader发送来的事件我们都需要传递给应用,比如上节讲到的翻盖/滑盖事件,除此之外的按键,触屏,轨迹球(后两者统一按 motion事件处理),也会有部分的事件被丢弃,InputDispatcher总会根据一些规则来丢弃掉一部分事件,我们来分析以下哪些情况下我们需 要丢弃掉部分事件?

    InputDispatcher.h中定义了一个包含有丢弃原因的枚举:

  1. enum DropReason {  
  2.     DROP_REASON_NOT_DROPPED = 0,  
  3.     DROP_REASON_POLICY = 1,  
  4.     DROP_REASON_APP_SWITCH = 2,  
  5.     DROP_REASON_DISABLED = 3,  
  6. };  

    1. DROP_REASON_NOT_DROPPED

     不需要丢弃

    2. DROP_REASON_POLICY

   设置为DROP_REASON_POLICY主要有两种情形:

    A. 在InputReader notify InputDispatcher之前,Policy会判断不需要传递给应用的事件。如上一节所述。

    B. 在InputDispatcher dispatch事件前,PhoneWindowManager使用方法interceptKeyBeforeDispatching()提前consume掉一些按键事件,如上面的流程图所示。

    interceptKeyBeforeDispatching()主要对HOME/MENU/SEARCH按键的特殊处理,如果此时能被consume掉,那么在InputDispatcher 中将被丢弃。

    3.DROP_REASON_APP_SWITCH

    当有App switch 按键如HOME/ENDCALL按键发生时,当InputReader向InputDispatcher 传递app switch按键时,会设置一个APP_SWITCH_TIMEOUT 0.5S的超时时间,当0.5s超时时,InputDispatcher 尚未dispatch到这个app switch按键时,InputDispatcher 将会丢弃掉mInboundQueue中所有处在app switch按键前的按键事件。这么做的目的是保证app switch按键能够确保被处理。此时被丢弃掉的按键会被置为DROP_REASON_APP_SWITCH。

    4. DROP_REASON_DISABLED

    这个标志表示当前的InputDispatcher 被disable掉了,不能dispatch任何事件,比如当系统休眠时或者正在关机时会用到。