第二章 先从看得到的入手——探究活动
2.1活动是什么
活动(Activity)是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或者多个活动。
2.2活动的基本用法
2.2.1手动创建活动
创建一个新项目,可以命名为【First_Activity】注意Activity选择【No Acrtivity】,因为我们要手动创建活动;
创建完成后【com.example.activitytest】目录是空的。
右键【com.example.activitytest】包->New->Activity->Empty Activity,在弹出的对话框中不要勾选【Generate Layout File】和【Launcher Activity】两个选项。(Generate Layout File表示为活动自动创建一个对应布局;Launcher Activity 表示会将活动设为当前项目主活动)
你需要知道,项目中任何活动都要重写Activity的【OnCreate()】方法,你新建的活动Android Studio会为你自动重写一个默认的。
2.2.2创建和加载布局
右键【app/src/main/res】目录->New->Directory,会弹出一个新建目录的窗口,这里先创建一个名为【Layout】的目录;
然后右键【Layout】目录->New->Layout resource file,又会弹出一个新建布局资源文件的窗口,我们将这个布局文件命名为【first_layout】,根元素就默认选择为【LinearLayout】
完成后会看到【first_layout】的布局编辑器;你可以使用code即代码模式编辑,也可以使用design即可视化模式进行编辑;
试着添加一个button;@id是引用一个既有id,@+id则是定义一个新id。
<?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" android:orientation="vertical" android:visibility="visible"> <Button android:id="@+id/button_1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="button_1" /> </LinearLayout>
这样一个布局就创建好了,可以在【First_Activity】的【onCreate】方法中加载这个布局:setContentView(R.layout.first_layout);
2.2.3在AndroidManifest文件中注册
活动的注册要放在【application】标签内,这里通过【activity】标签来对活动进行注册。使用【android:name】来指定注册的活动。
光注册还不行,还需要为程序配置主活动,这样程序运行起来才知道首先启动哪个活动。配置方法就是在【activity】标签内加入【intent-filter】标签并在这个标签内添加两句声明即可。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.activitytest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ActivityTest"> <activity android:name=".FirstActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest>
2.2.4在活动中使用Toast
需要定义一个Toast出发点,可以将触发点绑定在button的点击处理上:
在【onCreate】方法中添加button1的对象和点击处理函数;
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(); } });
2.2.5在活动中使用menu
menu即Android提供的一种菜单实现方式。
在【res】目录下新建一个【menu】文件夹,右键【res】->New->Directory,输入文件夹名【Menu】,点击【OK】;
在【Menu】目录下新建一个【main】菜单文件,右键【menu】->New->Menu resource file;
文件名输入【main】,点击【OK】创建完成,然后再main.xml中添加如下代码:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/add_item" android:title="Add"/> <item android:id="@+id/remover_item" android:title="Remove"/> </menu>
这里创建了两个菜单项,id是唯一标识符,title是名称。
接着回到【FirstActivity】中重写【onCreateOptionsMenu】方法,重写方法可以使用【Ctrl】+O快捷键。添加如下代码:
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main,menu); return true; }
仅仅显示出来菜单是不够的,还需要定义它的响应事件,需要在【FirstActivity】中重写【onOptionsItemSelected】方法:
@Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { switch(item.getItemId()){ case R.id.add_item: Toast.makeText(this,"you clicked Add",Toast.LENGTH_SHORT).show(); break; case R.id.remover_item: Toast.makeText(this,"you clicked remove",Toast.LENGTH_SHORT).show(); break; default: } return true; }
重新运行程序你会发现标题栏右侧多了三个点,这个就是菜单按钮了。
调用finish()方法就可以销毁当前活动了:
button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } });
2.3使用Intent在活动之间穿梭
Intent是Android程序中各组件之间进行交互的一种重要方式,不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。
Intent一般可被用于启动活动、启动服务、发送广播等场景。
Intent大致可以分为两种:显示Intent和隐式Intent。
2.3.1使用显示Intent
使用:先新建一个活动SecondActivity,然后在FirstActivity的点击按钮中添加如下代码:
button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(FirstActivity.this,SecondActivity.class); startActivity(intent); } });
2.3.2使用隐式Intent
隐式Intent并不明确的指出想要启动哪一个活动,而是指定一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。
使用:通过<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action和category,打开AndroidManifest.xml,添加如下代码:
<activity android:name=".SecondActivity"> <intent-filter> <action android:name="com.example.activitytest.ActionStart" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
这里指明,当前活动可以响应【com.example.activitytest.ActionStart】这个action,而【category】标签则包含了一些附加信息,只有<action>和<category>中的内容同时匹配上Intent中指定的【action】和【category】时,这个活动才能响应该Intent。修改FirstActivity中的按钮点击事件,代码如下:
public void onClick(View v) { Intent intent = new Intent("com.example.activitytest.ActionStart"); startActivity(intent); }
这样该Intent中指定的【action】和【category】与second活动就匹配了;重新运行程序,点击按钮,就能成功启动SecondActivity了。
每个Intent中只能指定一个【action】,却能指定多个【category】。使用【addCategory】方法,代码如下:
public void onClick(View v) { Intent intent = new Intent("com.example.activitytest.ActionStart"); intent.addCategory("com.example.activitytest.MyCategory") startActivity(intent); }
运行起来再次点击按钮就崩溃,因为没有活动可以匹配这个Intent,可以在SecondActivity的<intent-filter>标签中新增一个【category】声明,代码如下:
<activity android:name=".SecondActivity"> <intent-filter> <action android:name="com.example.activitytest.ActionStart" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="com.example.activitytest.MyCategory"/> </intent-filter> </activity>
这样就又正常了。
2.3.3更多隐式Intent用法。
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,屌不屌?比如你想在点击按钮后迁移到一个网页(百度),你可以添加如下代码:
public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://baidu.com")); startActivity(intent); }
启动程序,点击按钮试试,开心不?
事实上只是因为百度页面的活动的<data>标签响应了Intent的http协议,你完全可以自己建一个活动,让它也能相应打开网页的Intent。
你试试将SecondActivity的注册信息修改如下:
<activity android:name=".SecondActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="http"/> </intent-filter> </activity>
你发现再次点击按钮,你可以选择你建的这个项目作为响应程序了,并且可以迁移到SecondActivity活动中。
<data>标签不仅可以配置协议部分(scheme),还可以配置以下内容:【host】【port】【path】【nimeType】
并且除了【http】协议,也可以指定其他协议,比如以下代码可以在点击按钮后调用系统的拨号界面:
public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); }
2.3.4向下一个活动传递数据
很简单,Intent提供【putExtra】方法的重载,使你可以将数据存放在Intent中,直到启动下一个活动,在下一个活动中取出这些数据。存放代码如下:
public void onClick(View v) { Intent intent = new Intent(FirstActivity.this,SecondActivity.class); String data = "Hello SecondActivity"; intent.putExtra("extra_data",data); startActivity(intent); }
第一个参数是键,用于后面从Intent中取值,第二个参数是要传递的数据。
取出数据代码如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second_layout); Intent intent = getIntent(); String data = intent.getStringExtra("extra_data"); }
2.3.5返回数据给上一个活动
即调用【startActivityForResult】方法,该方法可以在活动销毁的时候返回一个结果给上一个活动;该方法接受两个参数,第一个参数是【Intent】,第二个参数是【请求码】。
2.4活动的生命周期
2.4.1返回栈
活动既是画页,放在返回栈中;
2.4.2活动状态
运行状态:栈顶
暂停状态:不在栈顶,仍可见(即上面的活动没有占满全屏)
停止状态:不在栈顶,也不可见;
销毁状态:从栈中移除。
2.4.3活动的生存期
onCreate()。相当于load。
onStart()。在活动由不可见变为可见状态时调用,相当于back后的fore。
onResume()。在活动准备好和用户交互是调用,这时活动一定是栈顶并处于运行状态。
onPause()。在系统准备启动或恢复另一个活动时调用。暂停状态
onStop()。活动完全不可见时调用。停止状态
onDestroy()。活动销毁前调用,之后活动变为销毁状态
onRestart()。在活动又停止状态变为运行状态前调用,也就是活动由不可见重新到栈顶了。
以上七个方法除了【onRestart,其他都是两两相对的,从而又可以把活动分为三种生存期:
完整生存期:活动从被创建到被销毁,即【onCreate】到【onDestroy】,一般活动在【onCreate】完成初始化操作,并在【onDestroy】中释放资源。
可见生存期:从【onStart】到【onStop】,可以通过这两个方法合理的管理那些对用户可见的资源。比如在【onStart】加载一些资源,在【onStop】释放,从而减小内存占用。
前台生存期:从【onResume】到【onPause】,这期间活动处于运行状态,可以和用户进行交互。
2.4.5活动被回收了怎么办
主要针对停止状态的活动由于系统内存不足被系统回收的情况,Activity提供了一个【onSaveInstanceState()】方法,这个方法在活动被回收前被调用,可以通过该方法保存数据。
该方法会携带一个【Bundle】类型的参数,【Bundle】提供一系列方法用于保存数据,比如可以使用【putString】保存字符串。使用【putInt()】保存整数。
保存到数据在onCreate中就可以取出来,你发现omCreate方法参数正是【Bundle】了吗。
2.5活动的启动模式
四种,可以在【AndroidManifest.xml】中通过给<activity>标签指定【android:launchMode】来选择启动模式。
standrad:默认启动模式,可以在栈中有多个实例,每次启动都是新创建一个该活动。
singleTop:在启动时如果发现栈顶已经是该活动,就直接使用,不在新创建。
singleTask:栈中只有一个该活动实例,栈中没有才创建。如果有了就将该活动上面的活动都出栈。replace。
singleInstance:该活动独享一个返回栈,实现程序间活动实例共享。就是lib。
2.6活动的最佳实践
这一节教你一些设计思想,比如给所有活动一个base类,用以添加共同逻辑(比如添加活动迁移打印等)。
比如写一个管理类,用以管理所有活动,