本文转自:数码林网站分析博客
原文地址:http://blog.digitalforest.cn/web-analytics-bother-strategy2
一、负责网站的技术部门和负责广告的营销部门行动很不协调
经常会听到这样的抱怨:原本以为精心策划的营销活动会增加网站的会员注册量,但是由于网站方面由其他部门负责所以无从插手,最终结果并不理想,网站的会员注册人数没有按照预期增加。
如果是大公司(有一定规模的)的话,广告常由营销部门负责,网站的制作和更新由信息技术部门负责,但有关网站的整体部署和预算分配给了不同的部门。各个部门都在为了自己部门的目标而努力,但却忽视了整体目标,这样往往是徒劳的,换句话说就是「部分的最优化并不代表整体的最优化 」。很多公司都面临着这样的问题。 想解决这类问题的关键在于:KPI (了解KPI请参考:“KPI精选连载 ”)的设定,而且要定期的共享这些KPI 。KPI的设定要以网站整体所处的商务位置和网站的整体目的为基础,然后再将各个KPI的设定分配给相应的部门。遗憾的是,很多公司KPI的设定是按部门分别进行设定的,像这样的KPI只能说是达到了部门的最优化。
设定KPI不是只为了收集数据,还要定期的把这些数据共享给各个相关部门,并制定出 对网站整体来说最佳的行动策略 ,这样才能使网站的目标达成变得更加容易。
(版权归数码林网站分析博客 所有,欢迎转载,但转载请注明出处。)
二、不知道现有的网站策略是否正确网站策略常常不是三言两语就能解释清楚的。
以网站导航策略举例来说: 为不同的目标访客设定了不同的导航路线,对这些导航路线分别进行效果验证是非常重要的 :用户是否按照预想的路线进行网页的浏览,如果没有按照预想路线访问网页的话,其原因是什么?这样就可以明确应该改善的重点。
很多网站都是以新访客为对象设计制作,但究竟这些新访客是否给网站带来了更多的转化呢?对这一效果进行检证也是很重要的。网站的访问者中新访客虽然很多,但常常达成转化的多为老访客。当明确了老访客的转化率相对新访客更高的情况下,应该针对老访客 进行 内容 更新, 发送回访邮件等改善措施。
三 、想要很好的区别于竞争对手网站,首先应该做些什么呢
要想与竞争对手的网站有所区别,可以从两个方面入手。一方面在网站宣传时,以本网站的强项作为宣传重点;另一方面就是在网站的功能和可用性方面加以区别。
1. 网站宣传时 以本网站的强项作为宣传重点
首先运用营销策略的基本分析方法:SWOT 和3C 的分析方法,从调查自己公司的强项和优势(与其他公司相比存在的优势)入手。人们常常认为对自己公司的强项很了解,但要用语言去表达却什么也说不出来了,所以应该在大家面前强调公司强项和优势,在此基础上去找出公司宣传的重点。
(版权归数码林网站分析博客 所有,欢迎转载,但转载请注明出处。)
2. 在网站 的功能和可用性方面加以区别
很多时候网站上登载的内容都是用企业术语编写的,对于用户来说理解起来很困难,他们往往会认为是不是自己找错了地方,就离开了网站。所以在网站追求区别化之前,要充分关注:网站对于用户来说是不是可用性很强、网站是否为用户提供了他想要的信息、用户对网站的满意程度如何等。
在Windows 8中,Animation角色的重要性超出了我们的想象,因为它也算是Windows 8流畅性体验的一部分。在我看来,Animation要做的好,有利于整体的布局,自定义控件,以及控件的行为的设计等等,都是为了用户体验。
通过定义ControlTemplate,我们可以为完全重新定义我们控件外观,而ControlTemplate中最重要的部分就是用来承载它外观的Visual Tree,这个模板必须包含如下的代码,即在发生适当的条件时,控件的外观应该是如何表现的。比如Button的Pressed状态的发生等等,虽然改变Button的外表状态不像是我们想的那种花时间,复杂的动画改变状态(比如以后会介绍的ObjectAnimation),但是也归为了Animation功能。
WinRT的Animation是基于时间的动画,举个例子,当一个线程既要跑动画,又要做其他工作而导致动画丢失了一些执行时间时,基于帧(Frame-based)的动画会继续从它上次离开的地方继续执行,而基于时间(Time-based)的动画,则根据现在实际的时间,来做原来计划中这个时间点应该做的效果。个人推断,一种极端是若是线程很busy,而分配的动画时间可能是2秒,那么线程若做别的事情超过2秒,动画将会一步到位,没有2秒的过程。
基础动画入门
下面对TextBlock的FontSize进行动态改变,这样TextBock的字体就会根据我们预设的方案改变,注意,其实Animation只是我们的控制方案,也就是Control,它的目标是某个属性,而不是整个动画怎么移动,有点像指挥家,它只负责指挥,具体的动作还是通过框架的渲染系统根据属性的改变而进行重新绘制,进而达到动画的效果. 一般情况下,我们的动画控制被当作是一种资源,放在Xaml中Root元素的Resources中.一个简单的Animation包括一个Storyboard和一个XXXAnimation,如下代码片段:
<Page … > <Page.Resources> <Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" To="144" Duration="0:0:3" /> </Storyboard> </Page.Resources> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock Name="txtblk" Text="Animated Text" Grid.Row="0" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center" /> <Button Content="Trigger!" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick" /> </Grid> </Page>
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } void OnButtonClick(object sender, RoutedEventArgs args) { (this.Resources["storyboard"] as Storyboard).Begin(); } }
在上述代码中,DoubleAnimation的目标类型是Double(它的类名修饰说明了这一切),也就是说,你可以控制某个控件的Double属性(必须是依赖属性),让它跟着你的”剧本”走.那”剧本”就是DoubleAnimation,而剧本的内容就是它的一些个属性.你将会看到,WinRT还提供了对Point,Color和Object(第一次看见对Object,好像它是我们唯一需要使用的,因为任何的类都是继承于它,事实是我们使用它只是为了重新给某个属性的赋值而已,一般情况就设定两个值交替使用)。
WinRT要求,一个Animation对象(比如DoubleAnimation)必须包含在Storyboard下。相反,一个Storyboard可以包含多并发执行动画的Animation对象,而它的职责就是确保它们正确,同步地执行。
上面的代码中的TargetName和TargetProperty是Storyboard定义的附加属性(attached properties),你可以在Animation对象中设置它所关心的对象名称以及对象的那个需要动画的属性。
如前面所说的,Animation只对某个值(依赖属性)感兴趣,而其实对动画的实现不感兴趣,或者说,你有计划地改变某个值完全可以不是为了动画,所以,Animation一般在其他的线程下执行(非UI线程),为的是UI线程可以响应用户的操作。然而,我们当前的操作是为了动画,大家知道,在非UI线程中不能直接去修改在UI线程中创建的控件属性(抛异常),因为它的改变会引起布局的重新刷新,即非UI线程直接修改了界面,这显然是不可取的。WinRT不希望animation运行在UI线程中,但是为了让动画代码能在UI线程下运行,你必须将DoubleAnimation的EnableDependentAnimation设置为True,来告诉WinRT这个动画是依赖于UI线程才能达到代码目的的(dependent on the user interface thread)。
剩下的From ,To,Duration属性就非常容易理解,即在3秒的时间内,FrontSize从1变到144.Duration的格式是”时:分:秒”或“时:分”或“时”,且对于秒来说,可以是小数。
当你还没按下Trigger按钮,那么文本的字大小是预设的48-pixel,一旦OnButtonClick触发了,那么动画便随着Begin()的调用而启动,然后这个文本的文字大小瞬间变成1(From的值),然后3秒后,线性地变到144(To的值).所谓线性的,那就是每秒变大的速度是一样的,如下计算:
v(单位pixel/second,p/s)=(144-1)/3=48-1/3(p/s)
字体大小(pixel) 时间(s) 1+v*0=1 0 1+v*1=48-2/3 1 1+v*2=96-1/3 2 1+v*3=144 3
你可以随时地触发Begin,即使它还在执行,也会重新从1pixel开始变大。
其他属性对Animation变化的影响
以DoubleAnimation为例,在之前的代码中,动画结束后,字体的大小不是恢复到48(预设值),而是被重新回归到To的值,也就是1pixel,就好像它准备从头再来一遍一样。控制这种行为的是Animation的FillBehavior,它是一个枚举值,默认是HoldEnd,也就是我们碰到的情况,若是改成Stop,那么我们的文本的值将会回归到48,也就是停止(stop)再次循环.修改代码如下:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" FillBehavior="Stop" From="1" To="144" Duration="0:0:3" /> </Storyboard>
我们除了可以使用From,To进行控制Double的始末值,还可以单用其中一个,或者使用By属性。
几种情况看代码:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" Duration="0:0:3" /> </Storyboard>
缺省To,这种情况相当于To值为当前预设的48pixel。
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" To="144" Duration="0:0:3" /> </Storyboard>
缺省From,相当于From值为预设值48pixel。
其实以上的From和To都是nullable类型,系统通过这样来判定它们是否有被赋值,若没有,则结合当前的预设值进行动画。
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" By=”100” Duration="0:0:3" /> </Storyboard>
使用By,那么就从当前的48pixel在3秒内增加100pixel,而且当你下次再开始动画时,它的值将从148开始增加到248,也就FillBehavior的HoldEnd不起作用,但是若设置成Stop,则它还是会回到48。
若你想让你的动画原路返回(对于以上的例子,相当于To变成1,From变成144,也是在3秒的时间内完成) ,那么可以将Animation的AutoReverse设置为true。这样,整个的动画就会花费6秒的时间:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" AutoReverse="True" From="1" To="144" Duration="0:0:3" /> </Storyboard>
以上是定义一个周期的,那么我们也可以通过设置Animation的RepeatBehavior来定义周期的数目(或者定义总的执行时间),以下的代码将执行3个周期,一个周期的定义是从1变到144,再从144变到1,一个周期6秒,总时间18秒:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" AutoReverse="True" RepeatBehavior="3x" From="1" To="144" Duration="0:0:3" /> </Storyboard>
RepeatBehavior也可以定义一个时间段(duration),比如我们可以让周期为6秒的动画执行7.5秒,那么6秒一个周期后,它又继续重新开始执行1.5秒,也就是final 的大小是1+v*1.5=72.5,它停止的时的大小将会保持在72.5,当然你还是可以使用FillBehavior设置成Stop来跳回到48。
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" AutoReverse="True" RepeatBehavior="0:0:7.5" From="1" To="144" Duration="0:0:3" /> </Storyboard>
RepeateBehavior还可以接受无数次的周期,只要输入”Forever”就可以:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" AutoReverse="Forever" RepeatBehavior="0:0:7.5" From="1" To="144" Duration="0:0:3" /> </Storyboard>
预约开始时间,是的,对于Animation还可以约定它什么时候开始动画,比如从现在开始1.5s后启动动画:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" BeginTime="0:0:1.5" From="1" To="144" Duration="0:0:3" /> </Storyboard>
到目前为止,我们所有的DoubleAnimation动画都是有固定的速度V的,也就是是线性的。那么,一种创建非线性动画的方法,就是设置DoubleAnimation的EasingFunction(缓动函数)属性的属性元素,可以设置的值是继承自EasingFunctionBase的11个类,比如:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" To="144" Duration="0:0:3" > <DoubleAnimation.EasingFunction> <ElasticEase /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard>
所谓的缓动函数,就是让动画不局限于一种物理状态(比如匀速),比如拥有加速度,而且是接近自然的,放松不死板的运动方式。当运行以上代码时,发现文本像是一个垂直于屏幕的弹簧,再你按下以后弹了好4次以后再停止,在此期间,时间还是3秒,第一次的最大值已经超过了144,然后第二次,第三次,最后停止在To的值上,即144,所以这符合对弹簧的模拟。
EasingFunctionBase 定义了一个EasingMode属性,当然也被其11个子类所继承。它的默认值是EasingMode.EaseOut,它的含义是动画效果开始的时候是线性地完成从1到144的变化(但是时间小于3秒,显然是为了后续的效果时间,但是总时间不会大于3秒),然后再进行“弹簧”的效果。你也可以将它的值设置为EaseIn,即效果在开始动画的时候演示,或者是用EaseInOut,在动画的开始和结束都进行特效。注意:个人体验这段 代码,使用EaseIn的时候,会触发"Invalid attribute value for property FontSize."的异常,导致程序中断。
一些基于EasingFunctionBase 的子类也定义了自己的属性,比如上面提到的ElasticEase类,它可以控制来回震荡的次数,即Oscillations属性,默认值是3。你可以设置它的值为10,那么它会来回弹10次然后停止,但是时间还是3秒。那么有控制次数的,也就有控制弹跳的效果的,即弹跳的程度,那么就是Springiness属性,它的中文意思是青春期,可以说,它的值越小,也就越“年轻”,所以弹跳的“动作幅度”也就会越大,默认值也是3。尝试如下的代码体验:
<Storyboard x:Key="storyboard"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" To="144" Duration="0:0:3" > <DoubleAnimation.EasingFunction> <ElasticEase Oscillations="10" Springiness="0"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard>
之前提到的,诸如DoubleAnimation的对象必须包含在Storyboard对象中,有趣的是,其实这两个类继承自同一个类,即TimeLine,类继承图如下:
Object DependencyObject Timeline Storyboard DoubleAnimation …
Storyboard定义了TimelineColletion类型的Children属性,以及附加属性TargetName和TargetProperty,除此之外,还定义了控制动画的过程的函数,如pause(暂停),resume(恢复)。
到目前为止,我们看见的AutoReverse,BeginTime,Duration,FillBehavior和RepeatBehavior都是定义在Timeline,那么由于这些是依赖属性,可想而知,在Storyboard上设置这些值以后,它的Children中的各种Animation就可以共享这些值。
Timeline也定义了一个名叫SpeedRatio的属性(Double类型),从名称可知是控制速度动画速度的,将原来的速度和它相乘后得出的速度就是最终速度,显然,这会影响时间,我们可以把要变化的值的范围看成是距离(等长的情况),那么时间和速度就成反比了(默认值当然就是1啦),运行如下代码:
<DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" To="144" Duration="0:0:3" SpeedRatio="0.5" > </DoubleAnimation>
你可以计算它的花费时间是6秒。在平时使用时,这个值一般放置在Storyboard(当然,本意是在哪个Timeline中都可以)
,这样的目的其实就是为了可以控制所有在Storyboard上的Animation,因为我们设计的时候,这些Animation将会以一个整体的效果出现。那么当我们在Storyboard和Animation中同时设置这个属性时,会发生什么情况呢,如运行一下的代码:
<Storyboard x:Key="storyboard" SpeedRatio="0.5"> <DoubleAnimation Storyboard.TargetName="txtblk" Storyboard.TargetProperty="FontSize" EnableDependentAnimation="True" From="1" To="144" Duration="0:0:3" SpeedRatio="2" > </DoubleAnimation> </Storyboard>
我们在Storyboard中设置速度为0.5,而在DoubleAnimation中设置速度为2,那么它的结果就是整个速度为1,也就是和将这个两个值去掉一样的效果,花费还是3秒。
Timeline还有一个Completed的事件,也就是当动画完成时会触发的事件,你可以在恰当的时候使用它。
接下去实践一下完全在C#代码中构建Animation,首先定义UI界面:
<Page …> <Page.Resources> <Style TargetType="Button"> <Setter Property="Content" Value="Trigger!" /> <Setter Property="FontSize" Value="48" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Margin" Value="12" /> </Style> </Page.Resources> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Click="OnButtonClick" /> <Button Grid.Row="0" Grid.Column="1" Click="OnButtonClick" /> <Button Grid.Row="0" Grid.Column="2" Click="OnButtonClick" /> <Button Grid.Row="1" Grid.Column="0" Click="OnButtonClick" /> <Button Grid.Row="1" Grid.Column="1" Click="OnButtonClick" /> <Button Grid.Row="1" Grid.Column="2" Click="OnButtonClick" /> <Button Grid.Row="2" Grid.Column="0" Click="OnButtonClick" /> <Button Grid.Row="2" Grid.Column="1" Click="OnButtonClick" /> <Button Grid.Row="2" Grid.Column="2" Click="OnButtonClick" /> </Grid> </Grid> </Page>
我们没有在XAML中定义任何关于Animation的信息,但是都在代码中添加。以上定义了9个独立的Button对象,我们在Click它的时候将对该Button中的Fontsize进行变化。构建的策略讨论,你可以为每个Button构建一次的Storyboard和DoubleAniamtion,然后在重复地使用它们,这里的条件是重复使用时,animation的target应该是同一个对象,也就是不能借给别人用,那么第二种策略就是每次使用新的Storyboard和DoubleAniamtion,简单的方法当然就是第二种,代码如下:
void OnButtonClick(object sender, RoutedEventArgs args) { DoubleAnimation anima = new DoubleAnimation { EnableDependentAnimation = true, To = 96, Duration = new Duration(new TimeSpan(0, 0, 1)), AutoReverse = true, RepeatBehavior = new RepeatBehavior(3) }; Storyboard.SetTarget(anima, sender as Button); Storyboard.SetTargetProperty(anima, "FontSize"); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(anima); storyboard.Begin(); }
和XAML中定义有点区别的是,由于XAML里只能通过为UI元素命名而相互引用,所以是使用Storyboard.SetTargetName来赋予动画的对象,但是在代码中,我们不需要知道它叫什么,而是关心它是什么,所以使用SetTarget,直接传递对象给它就可以。
还有,我们使用Duration控制动画的时间,那么可以使用一个接受TimeSpan对象的构造函数,那么我们在碰到Forever以及默认值(1秒)时,可以使用Duration的静态属性,一个是用Duration.Forever,一个是用Duration.Automatic。
当我们单击了Button时,它因为字体大小的改变而体积发生改变,那么Grid就需要重新计算如何分配每个单元格的大小,当我们多按几个Button时,你就会发现有趣的现象了。
Animation第一部分结束语:这是我看原版的Windows Programming 6th Release Preview电子版的度数笔记,是翻译和自己体会的结合。
这里还是以MTK4.0代码为基础。
Contacts流程:http://h529820165.iteye.com/blog/1677877
Phone.apk.
OutgoingCallReceiver.java
接受到Contacts传过来的广播后
public void onReceive(Context context, Intent intent) { ... Uri uri = intent.getData();; Intent newIntent = new Intent(Intent.ACTION_CALL, uri); newIntent.putExtra(Constants.EXTRA_IS_VIDEO_CALL, intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false)); if((PhoneNumberUtils.isUriNumber(number) && intent.getIntExtra(Constants.EXTRA_SLOT_ID, -1) == -1) || Constants.SCHEME_SIP.equals(uri.getScheme())) { startSipCallOptionHandler(context, newIntent); } else { newIntent.putExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL, number); newIntent.putExtra(Constants.EXTRA_SLOT_ID, intent.getIntExtra(Constants.EXTRA_SLOT_ID, 0)); PhoneApp.getInstance().callController.placeCall(newIntent); } }
这里将获取的uri,是否是video_call,number,slot封装在intent,此时action的值是:Intent.ACTION_CALL中,然后调用PhoneApp.getInstance().callController.placeCall。很容易就知道这里使用了单例模式。【因为这里有 intent.getIntExtra(Constants.EXTRA_SLOT_ID, -1) == -1这段代码,所以笔者猜测slot=-1时,是sip电话】
public void placeCall(Intent intent) { .... inCallUiState.clearProviderOverlayInfo(); ... CallStatusCode status = placeCallInternal(intent); ... switch (status) { case SUCCESS: case EXITED_ECM: if (status == CallStatusCode.EXITED_ECM) { inCallUiState.setPendingCallStatusCode(CallStatusCode.EXITED_ECM); } else { inCallUiState.clearPendingCallStatusCode(); } mApp.setBeginningCall(true); break; ..... case DROP_VOICECALL/ default: handleOutgoingCallError(status); break; } inCallUiState.latestDisconnectCall = null; inCallUiState.delayFinished = false; mApp.displayCallScreen(!intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false)); }
这里会根据状态码status 设置inCallUiState的一些属性值.如果返回Success/Exited_ecm,则开启PSonsor。不论返回何种状态,都会执行displayCallScreen,该方法会显示InCallScreen界面。该方法中有个极其重要的方法placeCallInternal(intent),该方法会去和ril层通信,执行真正的拨打电话的功能。
private CallStatusCode placeCallInternal(Intent intent) { int callStatus; if(PhoneApp.sVideoCallSupport && isVideoCall) { callStatus = VTCallUtils.placeVTCall(phone, number, contactUri, slot); } else { if(PhoneApp.sGemini) { callStatus = PhoneUtils.placeCallGemini(mApp, phone, number, contactUri, (isEmergencyNumber || isEmergencyIntent), inCallUiState.providerGatewayUri, slot); Profiler.trace(Profiler.CallControllerLeavePlaceCallInternal); } else { callStatus = PhoneUtils.placeCall(mApp, phone, number, contactUri, (isEmergencyNumber || isEmergencyIntent), inCallUiState.providerGatewayUri); } } switch (callStatus) { case Constants.CALL_STATUS_DIALED: ... if (exitedEcm) { return CallStatusCode.EXITED_ECM; } else { return CallStatusCode.SUCCESS; } break; case Constants.CALL_STATUS_DIALED_MMI: return CallStatusCode.DIALED_MMI; ..... } }
由于PhoneUtils.placeCallGemini/ PhoneUtils.placeCall会调用framework层函数,所以不再这里展开,将在后面分析如何和ril通信。
所以这里总结一下placeCall的功能:首先会和ril通信拨打电话,然后返回状态码,根据状态码的不同设置不同的inCallUiState属性值。 然后displayCallScreen启动InCallScreen界面。都是都是有CallController完成的。
void displayCallScreen(final boolean isVoiceOrVTCall) { ... try { Intent intent = isVoiceOrVTCall ? createInCallIntent() : createVTInCallIntent(); startActivity(intent); } catch (ActivityNotFoundException e) { } }
createInCallIntent()和createVTInCallIntent()的差别在于createVTInCallIntent()中多了intent.putExtra(Constants.EXTRA_IS_VIDEO_CALL, true);
static Intent createInCallIntent() { Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NO_ANIMATION); intent.setClassName("com.android.phone", getCallScreenClassName()); if (FeatureOption.MTK_TB_APP_CALL_FORCE_SPEAKER_ON == true) { intent.putExtra(InCallScreen.EXTRA_FORCE_SPEAKER_ON, true); } return intent; }
此时的action为Intent.ACTION_MAIN。然后启动InCallScreen activity.让我们来看看onCreat函数
protected void onCreate(Bundle icicle) { .... registerForPhoneStates(); if (icicle == null) { internalResolveIntent(getIntent()); } if (VTInCallScreenFlags.getInstance().mVTIsInflate && null != mVTInCallScreen ) { mVTInCallScreen.initVTInCallScreen(); } .... registerReceiver(.......) .... }
分别讲下这几个重要函数的作用:
registerForPhoneStates();监听电话的状态变化,如果变化则根据中间层的返回值,在inCallScreen中的handler中做出相应的处理。
mVTInCallScreen.initVTInCallScreen();实例化视频通话的界面,在onResume中,会根据当前状态决定是否关闭或者显示mVTInCallScreen。
registerReceiver(....) 这里会注册Intent.ACTION_LOCALE_CHANGED,Intent.ACTION_HEADSET_PLUG,以及mDMLockReceiver是否被运营商锁定的广播.
internalResolveIntent(getIntent()); 该方法会在onCreat和onNewIntent中调用
private void internalResolveIntent(Intent intent) { if (action.equals(intent.ACTION_MAIN)) { ..... if (FeatureOption.MTK_VT3G324M_SUPPORT == true) { if (getInVoiceAnswerVideoCall()) { setInVoiceAnswerVideoCall(false); } if (mCM.getState() == Phone.State.RINGING) { // When VT call incoming, use voice call incoming call GUI mVTInCallScreen.setVTVisible(false); mVTInCallScreen.setVTScreenMode(VTCallUtils.VTScreenMode.VT_SCREEN_CLOSE); } else if (intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false)) { // When dialing VT call, inflate VTInCallScreen mVTInCallScreen.initVTInCallScreen(); // When dialed a VT call, but dialed failed, needs not init state for dialing if (CallStatusCode.SUCCESS == mApp.inCallUiState.getPendingCallStatusCode()) { mVTInCallScreen.initDialingSuccessVTState(); } mVTInCallScreen.initDialingVTState(); mVTInCallScreen.initCommonVTState(); if (Phone.State.IDLE != PhoneApp.getInstance().mCM.getState() && !VTCallUtils.isVTCall(mCM.getActiveFgCall())) { // When voice is connected and place a VT call, need close VT GUI mVTInCallScreen.setVTScreenMode(VTCallUtils.VTScreenMode.VT_SCREEN_CLOSE); } else { mVTInCallScreen.setVTScreenMode(VTCallUtils.VTScreenMode.VT_SCREEN_OPEN); } } else { // set VT open or close according the active foreground call if (mCM.getState() != Phone.State.IDLE && PhoneUtils.isVideoCall(mCM.getActiveFgCall())) { if (DBG) log("receive ACTION_MAIN, but active foreground call is video call"); mVTInCallScreen.initVTInCallScreen(); mVTInCallScreen.initCommonVTState(); mVTInCallScreen.setVTScreenMode(VTCallUtils.VTScreenMode.VT_SCREEN_OPEN); } else { mVTInCallScreen.setVTScreenMode(VTCallUtils.VTScreenMode.VT_SCREEN_CLOSE); } } mVTInCallScreen.updateVTScreen(mVTInCallScreen.getVTScreenMode()); } } }
笔者省略掉了该方法中会判断是否打开键盘,是否开启扬声器的代码。 这里会根据是否是VIDEO_CALL和电话State给mVTInCallScreen设置不同的setVTScreenMode(这里有2种:VT_SCREEN_OPEN、VT_SCREEN_CLOSE),然后调用mVTInCallScreen.updateVTScreen()方法。onCreat执行完成后,紧接着是onResume()
protected void onResume() { ... final InCallUiState inCallUiState = mApp.inCallUiState; ... if (inCallUiState.showDialpad) { showDialpadInternal(false); // no "opening" animation } else { hideDialpadInternal(false); // no "closing" animation } ... if (bluetoothConnected) { setVolumeControlStream AudioManager.STREAM_BLUETOOTH_SCO); } else { setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); } SyncWithPhoneStateStatus status = syncWithPhoneState(); ...... }
在该方法中,会获取单例的InCallUiState,然后根据属性值决定相应的动作,如:是否打开拨号键盘等。这里说下比较重要的一个方法syncWithPhoneState(),该方法可以更新CallCard,就是说多路通话的时候,界面的更新入口就是syncWithPhoneState。【当第二路通话来的时候,和第一路通话的流程一模一样】
private SyncWithPhoneStateStatus syncWithPhoneState() { ... if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall() || hasPendingMmiCodes || hasPendingMmiCodes2 || showProgressIndication) { if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen..."); updateScreen(); return SyncWithPhoneStateStatus.SUCCESS; } ... }
mCM是CallManager的对象。
hasActiveFgCall() Return true if there is at least one active foreground call
hasActiveBgCall() Return true if there is at least one active background call
hasActiveRingingCall() Return true if there is at least one active ringing call
这里可以知道,如果有电话则更新屏幕updateScreen(),该方法又会调用updateScreen(false)
private void updateScreen(boolean force) { ... if (!isDialerOpened()) { mCallCard.updateState(mCM); } .... }
如果打开了键盘,则不更新callcard。mCallCard为CallCard的对象。
void updateState(CallManager cm) { ... Phone.State state = cm.getState(); // IDLE, RINGING, or OFFHOOK Call ringingCall = cm.getFirstActiveRingingCall(); Call fgCall = cm.getActiveFgCall(); Call bgCall = cm.getFirstActiveBgCall(); if ((ringingCall.getState() != Call.State.IDLE) && !fgCall.getState().isDialing()) { ... updateRingingCall(cm); }else if ((fgCall.getState() != Call.State.IDLE) || (bgCall.getState() != Call.State.IDLE)) { ... updateForegroundCall(cm); }else { ... updateNoCall(cm); } }
无论是updateRingingCall,updateForegroundCall,updateNoCall都会执行displayOnHoldCallStatus,该方法决定是否显示第二路通话。这里会执行updateForegroundCall()
private void updateForegroundCall(CallManager cm) { ... Call fgCall = cm.getActiveFgCall(); Call bgCall = cm.getFirstActiveBgCall(); ... displayMainCallStatus(cm, fgCall); Phone phone = fgCall.getPhone(); int phoneType = phone.getPhoneType(); if (phoneType == Phone.PHONE_TYPE_CDMA) { if ((mApplication.cdmaPhoneCallState.getCurrentCallState() == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) && mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) { displayOnHoldCallStatus(cm, fgCall); } else { //This is required so that even if a background call is not present // we need to clean up the background call area. displayOnHoldCallStatus(cm, bgCall); } } else if ((phoneType == Phone.PHONE_TYPE_GSM) || (phoneType == Phone.PHONE_TYPE_SIP)) { displayOnHoldCallStatus(cm, bgCall); } }
displayMainCallStatus(cm, fgCall); //Updates the main block of caller info on the CallCard该方法会执行updateSimInfo(call)等main block info
displayOnHoldCallStatus(cm, bgCall); //该方法会决定是否显示第二路通话