MainActivity如下:
package cc.testlaunchmode2; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.app.Activity; import android.content.Intent; /** * Demo描述: * Activity加载模式详解 * 在该Demo中主要讨论singleTask * * 技术描述: * Activity有四种加载模式--> * 1 standard: 标准(默认)模式 * 当通过这种模式来启动Activity时,Android总会为目标 * Activity创建一个新的实例,并将该Activity添加到当前 * Task栈中.注意,这种方式不会启动新的Task,只是将新的 * Activity添加到原有的Task * * 2 singleTop: Task顶单例模式 * 该模式和standard模式基本一致,但有一点不同:当将要被 * 启动的Activity已经位于Task栈顶时,系统不会重新创建 * 目标Activity实例,而是直接复用Task栈顶的Activity * * 3 singleTask: Task内单例模式 * 采用该加载模式时,Activity在同一个Task内只有一个实例. * 当系统采用singleTask模式加载Activity时,又分为以下 * 三种情况: * (1)如果将要启动的Activity不存在,那么系统将会创建该 * 实例,并将其加入Task栈顶 * (2)如果将要启动的Activity已存在,且存在栈顶,那么此时 * 与singleTop模式的行为相同 * (3)如果将要启动的Activity存在但是没有位于栈顶,那么 * 此时系统会把位于该Activity上面的所有其他Activity * 全部移出Task,从而使得该目标Activity位于栈顶 * * 4 singleInstance: 全局单例模式 * 在此种加载模式下,无论从哪个Task中启动目标Activity,只会 * 创建一个目标Activity实例且会用一个全新的Task栈来装载该 * Activity实例. * 当系统采用singleInstance模式加载Activity时,又分为以下 * 两种情况: * (1)如果将要启动的Activity不存在,那么系统将会先创建一个 * 全新的Task,再创建目标Activity实例并将该Activity实例 * 放入此全新的Task中 * (2)如果将要启动的Activity已存在,那么无论它位于哪个应用 * 程序,哪个Task中;系统都会把该Activity所在的Task转到 * 前台,从而使该Activity显示出来 * * Android对于Activity的管理方式--> * Android采用Task来管理多个Activity. * 当启动一个APP时,Android就会为之创建一个Task * 然后启动这个应用的入口Activity. * 但是Android并没有为Task提供API,开发者无法真正 * 地访问Task,只能调用Activity的getTaskId()方法来 * 获取其所在的Task的ID.事实上,我们可以把Task理解成 * Activity栈,Task以栈的形式来管理Activity:将不断启 * 动的Activity压入栈.即:先启动的Activity被放入栈底, * 后启动的Activity放在Task栈顶 * * 测试步骤及解释: * 1 进入MainActivity,点击按钮进入AnotherActivity. * 2 此时在AnotherActivity界面.目前在Task栈中有两个 * Activity.由下至上为:MainActivity-->AnotherActivity * 3 在AnotherActivity界面点击按钮,系统以标准模式 * 再次加载一个MainActivity * 4 此时在MainActivity界面.目前在Task栈中有三个Activity. * 由下至上为:MainActivity-->AnotherActivity-->MainActivity * 5 此时在MainActivity界面,再点击按钮准备进入AnotherActivity * 系统将以singleTask模式再次加载AnotherActivity.正因为 * 是以singleTask模式加载,所以会清除AnotherActivity上部 * 的所有Activity. * 6 此时在AnotherActivity界面.目前在Task栈中有两个 * Activity.由下至上为:MainActivity-->AnotherActivity * 这就和第二步的状态一致了 * * 备注说明: * 注意配置文件 * android:launchMode="standard" * android:name="cc.testlaunchmode2.MainActivity" * ............................................... * android:launchMode="singleTask" * android:name="cc.testlaunchmode2.AnotherActivity" * * 参考资料: * Android疯狂讲义(第二版) * Thank you very much */ public class MainActivity extends Activity { private Button mButton; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); init(); } private void init(){ mTextView=(TextView) findViewById(R.id.textView); mTextView.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId()); mButton=(Button) findViewById(R.id.button); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(MainActivity.this, AnotherActivity.class); startActivity(intent); } }); } }
AnotherActivity如下:
package cc.testlaunchmode2; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.app.Activity; import android.content.Intent; public class AnotherActivity extends Activity { private Button mButton; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.another); init(); } private void init(){ mTextView=(TextView) findViewById(R.id.textView); mTextView.setText("当前Activity:"+"\n"+this.toString()+"\n"+"当前TaskId:"+this.getTaskId()); mButton=(Button) findViewById(R.id.button); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(AnotherActivity.this, MainActivity.class); startActivity(intent); } }); } }
main.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Activity加载模式详解" android:layout_centerHorizontal="true" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="以singleTask方式启动AnotherActivity" android:layout_centerHorizontal="true" android:layout_marginTop="100dip" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout>
another.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Activity加载模式详解" android:layout_centerHorizontal="true" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="以standard方式启动MainActivity" android:layout_centerHorizontal="true" android:layout_marginTop="100dip" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout>
AndroidManifest.xml如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc.testlaunchmode2" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:launchMode="standard" android:name="cc.testlaunchmode2.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:launchMode="singleTask" android:name="cc.testlaunchmode2.AnotherActivity"> </activity> </application> </manifest>
StateListDrawable可以根据View的不同状态,更换不同的背景
可以应用如EditText,Button等中,以Button为例
系统中默认的按钮被按下的颜色和未点击时的颜色不一样,该种实现可以用Java代码和XML实现
以下是Java代码:
//……前面对Button的声明略去 okBtn.setBackgroundDrawable(addStateDrawable(this, R.drawable.btn_normal, R.drawable.btn_selected, R.drawable.btn_selected)); cancelBtn.setBackgroundDrawable(addStateDrawable(this, R.drawable.btn_normal, R.drawable.btn_selected, R.drawable.btn_selected)); //……对应主要的代码 //当对应的View处于不同的状态时,对应的bacdground跟着变化 private StateListDrawable addStateDrawable(Context context, int idNormal, int idPressed, int idFocused) { StateListDrawable sd = new StateListDrawable(); Drawable normal = idNormal == -1 ? null : context.getResources().getDrawable(idNormal); Drawable pressed = idPressed == -1 ? null : context.getResources().getDrawable(idPressed); Drawable focus = idFocused == -1 ? null : context.getResources().getDrawable(idFocused); //注意该处的顺序,只要有一个状态与之相配,背景就会被换掉 //所以不要把大范围放在前面了,如果sd.addState(new[]{},normal)放在第一个的话,就没有什么效果了 sd.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, focus); sd.addState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}, pressed); sd.addState(new int[]{android.R.attr.state_focused}, focus); sd.addState(new int[]{android.R.attr.state_pressed}, pressed); sd.addState(new int[]{android.R.attr.state_enabled}, normal); sd.addState(new int[]{}, normal); return sd; }
xml方式实现时,可以先了解下对应xml的语法
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize=["true" | "false"] android:dither=["true" | "false"] android:variablePadding=["true" | "false"] > <item android:drawable="@[package:]drawable/drawable_resource" android:state_pressed=["true" | "false"] android:state_focused=["true" | "false"] android:state_selected=["true" | "false"] android:state_active=["true" | "false"] android:state_checkable=["true" | "false"] android:state_checked=["true" | "false"] android:state_enabled=["true" | "false"] android:state_window_focused=["true" | "false"] /> </selector>
下面对应的具体实例,由于是做背景用,该xml将放于/res/drawable下面(StateList中第一个匹配当前状态的item会被使用。因此,如果第一个item没有任何状态特性的话,那么它将每次都被使用,这也是为什么默认的值必须总是在最后)
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/btn_selected"/> <item android:state_focused="true" android:drawable="@drawable/btn_selected"/> <item android:state_enabled="true" android:drawable="@drawable/btn_normal"/> <item android:drawable="@drawable/btn_normal" /> </selector>在Button的xml中进行加载:
<Button android:id="@+id/canel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_cancel" android:layout_margin="10dip" android:layout_weight="1" android:textColor="#ffffffff" android:textSize="15sp" android:background="@drawable/button_drawable" />
或在java代码中加载:
okBtn.setBackgroundDrawable(R.drawable.button_drawable);
效果都一样。
以下是从网络上抄录的一些Robotium注意事项
1)有些button没有string,没有text,只能通过index来click这样很不直观,而且button的index并不是固定的,有可能随着控件重新加载,顺序也有可能发生变化,无法保证测试结果。查看了robotium源码,发现大多数click方法最终都是通过传入参数转成view,再调用clickOnView,于是参照着写了一个通过button的ID来click的方法。Button的ID需要查看测试对象的源码中获取。比如导航中就有菜单栏大多数据button就是这种类型的。
2)listview动态添加item如何判断添加成功。可从添加前及添加后Item个数判断,先确定添加item的属性,再通过相应的方法获取item。比如添加一个item可能需要三个textview,那么通过getCurrentTextViews(View)前后获取到的个数差就相差三个。比如添加黑名单到黑名单列表。
3)有些EditText输入时调出输入法,但有些EditTextl输入时不会调出输入法,如果调出了输入法,需要goBack()关闭输入法才可进行下一步操作。比如手机防盗输入密码会调出输入法,酷盾帐号登录或是注册时不会调出输入法。
4)有些控件是开发自已写的,robotium没有相应的API可用,可以使用clickOnScreen()大概预估一个x,y值,0坐标在左上角。比如备份还原中从备份界面切换到酷盾帐号界面,黑名单中进入黑名单列表。
5)点击listview中随机添加的item。随机添加的item无法得知text,则可以通过clickInList(int line)。比如黑名单列表。
6)有些界面robotium提供的滚动功能不起作用,需要找当前activity屏幕之外的button或是其他view时,可以通过solo.sendKey()发送向上、向下或是左右移动焦点找到需要点击的button或是其他view。
4. 结果判断
4.1 waitForText
该方法适用于点击操作后需要一点时间才返回结果的结果判断。比如联网操作,可以设置适当的延时,等待返回结果,判断结果更加正确。
4.2 assertActivity
该方法适用于activity时,可以判断点击操作切换Activity是否正确,可以与waitfortext配合使用。
4.3 searchText+assert
当有editText时,输入内容后,可通过searchText查找输入内容是否是预期结果,再将返回结果判断。
注:有些editText的内容无法通过searchText,原因暂时没找到。比如:手动添加黑名单时的名称及号码的输入框。
5. 执行
有两种方式,一种是通过eclipse;一种是通过命令行。
5.1 eclipse
Run as Android JUnit Test,可以跑整个测试工程,也可以跑单个测试用例。
5.2 命令行
adb install ****Test.apk
adb shell am instrument testpackagename/android.test.InstrumentationTestRunner
抓取log的关键字是TestRunner。