Robolectric官网第一个例子
1.页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/login"
android:text="Login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
2.页面逻辑
public class WelcomeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome_activity);
final View button = findViewById(R.id.login);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));
}
});
}
}
3.单元测试
@RunWith(RobolectricTestRunner.class)
public class WelcomeActivityTest {
@Test
public void clickingLogin_shouldStartLoginActivity() {
WelcomeActivity activity = Robolectric.buildActivity(WelcomeActivity.class).create().get();
activity.findViewById(R.id.login).performClick();
Intent expectedIntent = new Intent(activity, LoginActivity.class);
Intent actual = shadowOf(RuntimeEnvironment.application).getNextStartedActivity();
assertEquals(expectedIntent.getComponent(), actual.getComponent());
}
}
方法介绍
1.@RunWith(RobolectricTestRunner.class) 通过此注解定义Robolectric运行的TestRunner
2.单元测试类上还可以添加@Config(application = X.class,
shadows = {A.class, B.class, C.class,…}) shows中是写的是本测试类中用到的实现的shadow类,application指定测试类使用的application类
3.Robolectric.buildActivity() 用于构造Activity,create()函数执行后,该Activity会运行到onCreate周期,resume()则对应onResume周期,一般情况下单元测试只要到create()状态就可以了,但是如果开发重写onstart()/onResume()方法(比如在这里进行了一些事件绑定),就需要运行到这些状态了
Android常用知识
1.Activity
1)一个activity指的是一个从创建到销毁过程的页面活动
2)activity和布局创建联系:
在oncreate时绑定setContentView(R.layout.xmlfilename);//给当前活动加载布局
3)所有的活动需要在AndroidManifest.xml中进行注册才能生效
将活动注册为主活动(app启动后首先打开的活动):
<activity android:name=".ActivityName" android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
4)销毁一个活动:finish();
5)使用Intent在活动之间穿梭(Intent不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据)
- 显式Intent:
Intent intent = new Intent(FirstActivity.class,SecondActivity.class);
startActivity(intent);
- 隐式Intent
- 向下一个活动传递数据
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);
startActivity(intent);
在SecondActivity获取数据
super.onCreate(savedInstanceState);
setContentView(R.layout.second_layout);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity", data); }
2.Activity的生命周期
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈
Oncreate(): 活动第一次被创建的时候调用,一般在此方法中完成活动的初始化操作,比如加载布局,绑定事件等
onStart(): 活动由不可见变为可见的时候调用
onResume(): 活动在准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶
onPause(): 在系统准备去启动或者恢复另外一个活动的时候调用,我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用(点击手机的home键会处于此状态)
onStop(): 这个方法在活动完全不可见的时候调用。它和onPause方法的区别主要在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行
onDestroy(): 这个方法在活动被销毁之前调用,之后活动的状态将变成被销毁状态
onRestart(): 这个活动在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
3.活动的启动模式
standard: 系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例(默认情况下就是这种模式)
singleTop: 在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例;但是如果不在栈顶就会再创建一个新的实例
singleTask: 每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个薪的活动实例
singleInstance: 定义为singleInstance模式的活动会启用一个新的返回栈来管理这个活动
4. 基本布局
- LinearLayOut线性布局:它包含的控件会在线性方向上依次排列。
注意: 如果:LinearLayOut的排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent,因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就没有位置放了,vertical排列一样 - RelativeLayOut相对布局:通过相对定位的方式让控件出现在布局的任何位置
- FrameLayOut帧布局:所有的控件都默认放在布局的左上角(多个会重叠在一起),也可以通过layout_gravity属性修改控件在布局中的对齐方式
- 百分比布局:可以指定控件在布局中所占的百分比
- 自定义布局,在布局文件中用include引入
5. 自定义控件
新建 TitleLayout 继承自 LinearLayout ,让它成为我们自定义的标题栏控件,代码如下所示:
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//R.layout.title title是一个我们自定义的控件xml文件名
//本文中未给出,假设title.xml文件中有一个返回button(id/title_back),
//一个title textView(id/title_text),一个编辑按钮(id/title_edit)
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button)findViewById(R.id.title_back);
Button titleEdit = (Button)findViewById(R.id.title_edit);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show();
}
});
}
}
在布局中引入TitleLayout控件,就会调用TitleLayout构造函数。
LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件ID,第二个参数给加载好的布局再添加一个父布局,这里我们想要指定为titleLayout,于是直接传入this
使用此控件方法:
<com.example.uicustomviews.TitleLayout android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
和普通的控件的使用基本一样,但是在添加自定义控件的时候,我们需要指明控件的完整类名,包名在这里是不可以省略的(java类)
6.Application
- 每当应用程序启动的时候,系统就会自动将这个类进行初始化。我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如全局context