LogCat
Android Studio中已经默认添加了LogCat工具,macOS使用control+6,Win使用Alt+6打开。
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下方法:
Log.v() //这个方法打印最为琐碎的,意义最小的日志信息,对应级别verbose
Log.d() //这个方法打印一些调试信息,对应级别debug
Log.i() //这个方法打印一些警告,提示程序的潜在风险,对应级别warn
Log.e() //这个方法用于打印错误信息,对应级别error
尝试
在onCreate()
方法中添加一行打印日志的语句。
Log.d(“HelloWorldActivity”, “onCreate execute”);
函数传入两个参数,第一个是tag
,一般传入当前的类名,主要用于过滤信息,第二个参数是msg
,即想要打印的具体内容。
Activity
创建活动
定义一个Activity
类的子类,在子类中重写onCreate()
方法。
创建和裁剪布局
在res/layout
目录中新建一个XML
布局文件。
尝试添加一个Button
。
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"
/>
其中@+id/button_1
是在XML语言中定义一个id
的语法。
把+
去掉,@id/button_1
就是引用id
的语法。
随后,android:layout_width
指定元素宽度,match_parent
表示和父元素一样。
android:layout_height
自然就是高度,wrap_content
表示恰好容纳就可以了。
android:text
指定元素中显示的文字内容。
应用布局
在onCreate()
中写入:setContentView(R.layout.first_layout);
传入一个布局文件的id
。
注册
所有的活动需要在AndroidManifest.xml
中注册才能生效。
<activity
android:name=".FirstActivity"
android:label="This is FirstActivity">
<intent-filter>
<action
android:name=
"android.intent.action.MAIN" />
<category
android:name=
"android.intent.category.LAUNCHER"/>
</intent-filter>
可以看到,活动的注册声明要放在<application>
标签内,这里是通过<activity>
标签来对活动进行注册的。
首先我们要使用android:name
来制定具体注册哪一个活动,这里的.FirstActivity
是com.example.activitytest.FirstActivity
的缩写。由于最外层的<manifest>
标签中已经通过package
属性指定了用户的包名,因此可以省略。
用android:label
指定活动中的标题栏的内容。
需要注意的是,给主活动指定的label
不仅会成为标题栏,而且还会成为Laucher
中应用程序的名称。
<intent-filter>
标签,中的<action android:name="android.intent.action.MAIN" />
和<category android:name="android.intent.category.LAUNCHER"/>
使这个活动成为这个程序的主活动。
如果一个程序没有主活动,那么一般这样的程序都是作为第三方服务供其他应用在内部进行调用的。
隐藏标题栏
只需要在setContentView()
方法之前加一句:
requestWindowFeature(Window.FEATURE_NO_TITLE);
在活动中使用Toast
只需要在需要触发Toast
的地方加上Toast.makeText
方法就行。
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(
FirstActivity.this,
"You clicked Button 1",
Toast.LENGTH_SHORT
).show();
}
});
在活动中使用Menu
先在res
中创建menu
文件夹,创建一个main.xml
布局文件,在XML
文件中创建菜单布局。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"
/>
<item
android:id="@+id/remove_item"
android:title="Remove"
/>
</menu>
在Activity
中重写onCreateOptionMenu
和onOptionItemSelected
方法。
public boolean onCreateOptionMenu(Menu menu){
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public boolean onOptionItemSelected(MenuItem item){
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
销毁一个活动
直接调用finish
方法即可。
使用Intent
Intent
可以在活动之间跳转。
使用显式Intent
新建一个layout
布局文件,在其中创建第二个活动的布局。
在AndroidManifest.xml
中进行注册。只需要注册一个name
即可。
在按钮的点击事件中添加代码:
public void onClick(View v) {
Intent intent = new Intent(
FirstActivity.this,
SecondActivity.class
);
startActivity(intent);
}
构建一个Intent
实例,传入FirstActivity.this
作为上下文,传入SecondActivity.class
作为活动目标。然后通过startActivity
方法来执行这个Intent
使用隐式Intent
隐式方法不明确支出想要启动哪一个活动,而是指定了一系列抽象的action
和category
等信息,由系统分析调用哪个活动。
首先在AndroidManifest.xml
中配置<intent-filter>
<intent-filter>
<action
android:name="com.example.activity.test.ACTION_START"
/>
<category
android:name="android.intent.category.DEFAULT"
/>
</intent-filter>
这个action
标签指明了当前活动可以响应com.example.activity.test.ACTION_START
这个action
,category
标签中包含一些附加信息,更精确地指明了当前活动能够相应的Intent
中可能包含category
。这两个属性同时匹配时才能相应该Intent
。
现在改变一下按钮的点击事件。
public void onClick(View v) {
Intent intent = new Intent(
"com.example.activity.test.ACTION_START"
);
startActivity(intent);
}
新的Intent
构造方法传入了一个action
字符串,可以直接相应。
这里没有设定category
,因为在注册表中注册的是DEFAULT
默认,会自动添加到Intent
。
如果我们给Intent
实例添加一个category
,intent.addCategory("com.example.activitytest.MY_CATEGORY");
这样就只能匹配特定的category
。同时,给注册表中再添加一个相同的category
,这样就能正常运行了。
更多隐式Intent
的用法
隐式Intent
不仅可以启动自己程序的活动,还可以启动其他应用程序的活动,这使得Android
多个应用程序之间的功能共享成为可能。
下面演示用隐式Intent
打开系统浏览器。
public void onClick(View v) {
Intent intent = new Intent(
Intent.ACTION_VIEW
);
intent.setData(Uri.parse(
"http://wzhzzmzzzy.farbox.com"
));
startActivity(intent);
}
这里指定action
是Intent.ACTION_VIEW
,这是一个Android
系统内置的动作,常量值为android.intent.action.VIEW
,然后通过Url.parse
方法,解析网址成一个Url
对象,调用setDate
将其传递进去。
<data>
标签
setData
方法可以接受一个Uri
对象,,指定当前Intent
正在操作的数据。通常数据都是以字符串的形式传入到Uri.parse
方法中解析产生。
与之对应,我们可以在<intent-filter>
标签中再配置一个<data>
标签,用于更加精确地指定当前活动能够响应什么类型的数据。下面是<data>
标签中可以配置的内容:
android:scheme
//用于指定数据的协议部分,如http
android:host
//用于指定数据的主机名部分,如www.baidu.com
android:port
//用于指定数据的端口部分,一般跟随在主机名后
android:path
//用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容
android:mimeType
//用于指定可以处理的数据类型,允许使用通配符的方式进行指定
只有<data>
标签中指定的内容和Intent
中携带的Data
完全一致,当前活动才能相应。只要设置android:scheme
为http
,就可以相应所有的http
协议的Intent
了。
向下一个活动传递数据
通过putExtra()
方法的重载,可以把我们想要传递的数据暂存在Intent
中,启动了另一个活动之后,只需要把这些数据再从Intent
中取出就可以了。比如说FirstActivity
中有一个字符串,现在想把它传递到下一个活动当中:
String s = "Hello SecondActivity";
Intent intent = new Intent(
FirstActivity.this,
SecondActivity.class);
intent.putExtra("extra_data", s);
然后再SecondActivity
中取出数据:
Intent i = getIntent();
String s = i.getStringExtra("extra_data");
Log.d("SecondActivity", s);
首先通过getIntent
方法获取到用于启动SecondActivity
的Intent
,然后调用getStringExtra
方法,传入相应的键值,就可以得到相应的数据了。如果传递int
就用getIntExtra
方法, 布尔值等以此类推。
返回数据给上一个活动
Activity
中有startActivityFroResult
方法,也是用于启动活动的,但是这个方法期望在活动销毁时返回一个结果给上一个活动。
该方法接受两个参数,第一个参数的Intent
,第二个参数是请求码,用于在之后的回调中判断数据来源。
Intent intent = new Intent(
SecondActivity.this, ThirdActivity.class
);
startActivityForResult(intent, 1);
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra(
"data_return",
"Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
});
在活动中构建一个Intent
用于传递数据。要传递的数据存放在Intent
中,然后调用了setResult
方法,这个方法专用于向上一个活动返回数据。
setResult
方法有两个参数,第一个用于向上一个活动返回处理结果,一般只用RESULT_OK
或者RESULT_CANCELED
这两个值,第二个参数将带有数据的Intent
传递回去。
由于我们使用startActivityForResult
方法,在下一个活动销毁之后会回调上一个活动的onActivityResult
方法,因此需要在FirstActivity
中重写来得到返回的数据。
onActivityResult
方法有三个参数,第一个参数请求码requestCode
,第二个参数是返回数据时传入的处理结果resultCode
,第三个是携带着返回数据的Intent data
。
因为一个活动中可能调用startActivityForResult
启动很多不同的活动,每个活动返回的数据都会回调,因此需要检查处理结果resultCode
来判断数据来源,确定数据是从SecondActivity
返回的之后,我们再通过resultCode
的值来判断处理结果是否成功。最后从data
中取值并且打印出来。
但是还有一个问题!如果通过Back
键来返回,那数据怎么带回来呢?这个时候需要重写一下onBackPressed
方法。
@Override
public void onBackPressed(){
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
活动的生命周期
返回栈
Android
中的活动是可以层叠的。每创建一个新活动,就会覆盖在原活动之上,点击Back
键就会销毁最上面的活动,下面的一个活动就会重新显示出来。
Android
是使用任务�(Task
)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack
)。每启动一个新活动,就会入栈,每当按下Back
或者调用finish()
,就会弹栈。每个引用程序都会有自己的返回栈。
活动状态
活动有四种状态:运行、暂停、停止、销毁。
在栈顶时运行、屏幕上可见时暂停、不再处于栈顶时停止、弹栈后销毁。
活动生存期
onCreate() //活动第一次创建时会调用
onStart() //活动由不可见变为可见是调用
onResume() //活动准备好与用户交互时调用,此时活动位于栈顶
onPause() //在系统准备去启动或者恢复另一个活动时调用,通常会释放一些占用CPU的数据,保存一些关键数据
onStop() //活动完全不可见时调用,如果新活动不是对话框式则调用onStop,否则调用onPause
onDestroy()//活动被销毁之前调用,之后活动将变为销毁状态
onRestart()//活动由停止变为运行之前调用
活动被回收了怎么办
如果活动被回收了,而弹栈之后到达了,就会再一次调用活动的onCreate
方法,唯一的问题是活动中暂存的临时数据都丢失了。Activity
中提供了一个onSaveInstanceState
的回调方法,这个方法会保证一定在活动被回收之前调用,因此可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
onSaveInstanceState
方法会携带一个Bundle
类型的参数,Bundle
提供了一系列的方法用于保存数据。每个方法需要传入两个参数,一个是键,一个是真正要保存的内容。
@Override
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
String tempData = "Somethin";
outState.putString("data_key", tempData);
}
数据保存之后,onCreate
方法中的Bundle
参数就会获得这些数据。只需要再次取出即可。
if (savedInstanceState != null){
String tempData =
savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
活动的启动模式
启动模式有四种,standard
、singleTop
、singleTask
、singleInstance
。在AndroidManifest.xml
中通过给<activity>
标签指定android:launchMode
属性来选择启动模式。
standard
standard
是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。在standard
模式加,每当启动一个新的活动,就会在返回栈中入栈,并且处于栈顶的位置。对于使用standard
模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
singleTop
模式下,如果启动活动时发现返回栈栈顶已经是这个活动,则认为可以直接使用它,不会创建新的实例。
singleTask
singleTop
模式可以很好地解决重复创建栈顶活动的问题,但是如果该活动没有处于栈顶位置,还是可能会创建多个活动实例。在singleTask
下,每次启动该活动室,系统首先会在返回栈中检查是否存在该活动的实例。如果发现已经存在则直接使用该实例,并把在这个活动之上的活动统统出栈,如果没有就创建新的实例。
singleInstance
这个模式算是四种启动模式中最特殊也最复杂的一个了。不同于以上三种,在singleInstance
模式下,活动会启用一个新的返回栈来管理(如果singleTask
模式指定了不同的taskAffinity
,也会启动一个新的返回栈)。举个例子,假设程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,就可以用singleInstance
模式创建一个单独的返回栈来管理这个活动,可以让任意应用程序来调用。
活动的最佳实践
了解当前是哪个活动
新建一个BaseActivity
类,重写onCreate
方法如下:
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
然后让每个活动类都改为继承自BaseActivity
,这样就可以在LogCat
中看到当前类名。
随时随地退出程序
当在程序运行中的一个活动,要退出可能会需要按多次Back
键,所以最好需要一个直接退出活动的方法。
新建一个ActivityCollector
类作为活动管理器:
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<Activity>();
public static void addActivity(Activity activity){
activities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for (Activity activity : activities)
if (!activity.isFinishing())
activity.finish();
}
}
接下来修改BaseActivity
:
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
在BaseActivity
的onCreate
方法中调用ActivityCollector
的addActivity
的方法,表明将当前正在创建的活动添加到活动管理器里,然后重写onDestroy
方法,并且调用了ActivityCollector
的removeActivity
方法,表明将一个马上要销毁的活动从活动管理器中移除。
以后,不管想在什么地方退出程序,只需要调用finishAll
就可以了。
启动活动的最佳写法
之前启动的方法是构建一个新的Intent
,然后调用startActivity
或者startActivityForResult
来启动活动。如果SecondActivity
需要用到两个字符串参数,在启动时必须要传递,可能会写成下面这样:
Intent intent = new Intent(FirstActivity.this,
SecondActivity.class);
intent.putExtra("extra_data1", s1);
intent.putExtra("extra_data2", s2);
startActivity(intent);
这样虽然可以,但是在SecondActivity
是一个黑盒子时,是会出现问题的。
可以修改SecondActivity
的代码:
public static void actionStart(Context context, String s1, String s2){
Intent intent = new Intent(
context,
SecondActivity.class
);
intent.putExtra("extra_data1", s1);
intent.putExtra("extra_data2", s2);
context.startActivity(intent);
}
这样写的好处是给以后需要启动SecondActivity
的活动提供了一个actionStart
方法来传入需要的参数,提升了代码的可读性和延续性。