安卓开发四大组件比较重要。其他的都相当于HTML+CSS+JS 没什么卵用,那些控件用到了再学就行了
四大组件:Activity,Content Provider,Broadcast Receiver, Service(按重要程度排序)
同时安卓版 mybatis--->ROOM 和 SQLite
掉包工具 Retrofit
Fragment livedata viewmodel Navigation Drawer
其他安卓UI: Material Design AndroidX
参考链接:
https://www.bilibili.com/video/BV19U4y1R7zV/?spm_id_from=333.337.search-card.all.click&vd_source=5e78312fb5f90b9c83a318189a70a5c0
Activity
所谓“打开页面”或“关闭页面”沿用了浏览网页的叫法,对于App
而言,页面的真实名称是“活动”—Activity。打开某个页面其实是启动某个活动,所以有startActivity
方法却无openActivity
方法
页面的调用和返回
// 活动类直接实现点击监听器的接口View.OnClickListener
public class ActStartActivity extends AppCompatActivity implements
View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_start);
// setOnClickListener来自于View,故而允许直接给View对象注册点击监听器
findViewById(R.id.btn_act_next).setOnClickListener(this);
}
@Override
public void onClick(View v) { // 点击事件的处理方法
if (v.getId() == R.id.btn_act_next){
// 从当前页面跳到指定的新页面
//startActivity(new Intent(ActStartActivity.this,ActFinishActivity.class));
startActivity(new Intent(this, ActFinishActivity.class));
}
}
}
# 按钮
<?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:gravity="center">
<Button
android:id="@+id/btn_act_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳到下个页面" />
</LinearLayout>
调用之后的返回
在Java代码中,调用finish方法即可关闭当前页面,前述场景要求点击箭头图标或完成按钮都返回上一页面,则需给箭头图标和完成按钮分别注册点击监听器,然后在onClick方法中调用finish方法。
public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_finish);
findViewById(R.id.iv_back).setOnClickListener(this);
findViewById(R.id.btn_finish).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
// 结束当前的活动页面
finish();
}
}
}
以下为前端代码
<?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">
<ImageView
android:id="@+id/iv_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="5dp"
android:src="@drawable/ic_back" />
<Button
android:id="@+id/btn_finish"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="完成" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按返回键,或者点击左上角的箭头图标,或者点击上面的完成按钮,均可关闭当前页面、返回上个页面" />
</LinearLayout>
Activity生命周期(非常重要)
App引入活动的概念而非传统的页面概念,这是有原因的,单从字面意思理解,页面更像是静态的,而
活动更像是动态的。活动有从含苞待放到盛开再到凋零的生命过程。
每次创建新的活动页面,自动生成的Java代码都给出了onCreate方法,该方法用于执行活动创建的相关操作,包括加载XML布局、设置文本视图的初始文字、注册按钮控件的点击监听,等等。onCreate方法所代表的创建动作,正是一个活动最开始的行为,除了onCreate,活动还有其他几种生命周期行为。
它们对应的方法说明如下:
- onCreate:创建活动。此时会把页面布局加载进内存,进入了初始状态。
- onStart:开启活动。此时会把活动页面显示在屏幕上,进入了就绪状态。
- onResume:恢复活动。此时活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的
- 点击动作、允许用户输入文字等。
- onPause:暂停活动。此时活动页面进入暂停状态(也就是退回就绪状态),无法与用户正常交互。
- onStop:停止活动。此时活动页面将不在屏幕上显示。
- onDestroy:销毁活动。此时回收活动占用的系统资源,把页面从内存中清除掉。
- onRestart:重启活动。处于停止状态的活动,若想重新开启的话,无须经历onCreate的重复创建过程,而是 走onRestart的重启过程。
- onNewIntent:重用已有的活动实例。
上述的生命周期方法,涉及复杂的App运行状态,更直观的活动状态切换过程如图4-2所示。
Activity的启动模式(也很重要 暂时略)
Activity的信息传递
显式Intent和隐式Intent
Intent是各个组件之间信息沟通的桥梁,既能在Activity之间沟通,又能在Activity与Service之间沟通,也能在Activity与Broadcast之间沟通。总而言之,Intent用于Android各组件之间的通信,它主要完成下列3部分工作
(1)标明本次通信请求从哪里来、到哪里去、要怎么走。
(2)发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
(3)发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。
为了做好以上工作,就要给意图配上必需的装备,Intent的组成部分见表
1.显式Intent,直接指定来源活动与目标活动,属于精确匹配
在构建一个意图对象时,需要指定两个参数,第一个参数表示跳转的来源页面,即“来源Activity.this”;第二个参数表示待跳转的页面,即“目标Activity.class”。具体的意图构建方式有如下3种:
Intent intent = new Intent(this, ActNextActivity.class); // 创建一个目标确定的意图
Intent intent = new Intent(); // 创建一个新意图
intent.setClass(this, ActNextActivity.class); // 设置意图要跳转的目标活动
Intent intent = new Intent(); // 创建一个新意图
// 创建包含目标活动在内的组件名称对象
ComponentName nent = new ComponentName(this, ActNextActivity.class);
intent.setComponent(component); // 设置意图携带的组件信息
2.隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配(较为重要)
通常App不希望向外部暴露活动名称,只给出一个事先定义好的标记串,这样大家约定俗成、按图索骥
就好,隐式Intent便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已
有的系统动作。常见系统动作的取值说明见表4-4。
以下为一个拨号、短信功能的实现
<?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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="点击以下按钮将向号码12345发起请求" />
<Button
android:id="@+id/btn_dial"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到拨号页面" />
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到短信页面" />
<Button
android:id="@+id/btn_my"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="跳到我的页面" />
</LinearLayout>
对应的后端
动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。
当然,由于动作是模糊匹配,因此有时需要更详细的路径,比如仅知道某人住在天通苑小区,并不能直
接找到他家,还得说明他住在天通苑的哪一期、哪栋楼、哪一层、哪一个单元。Uri和Category便是这样
的路径与门类信息,Uri数据可通过构造函数Intent(String action, Uri uri)在生成对象时一起指定,也可
通过setData方法指定(setData这个名字有歧义,实际相当于setUri);Category可通过addCategory
方法指定,之所以用add而不用set方法,是因为一个意图允许设置多个Category,方便一起过滤。
public class ActionUriActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_action_uri);
findViewById(R.id.btn_dial).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
findViewById(R.id.btn_my).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
String phoneNo = "12345";
switch (v.getId()) {
case R.id.btn_dial:
// 设置意图动作为准备拨号
intent.setAction(Intent.ACTION_DIAL);
// 声明一个拨号的Uri
Uri uri = Uri.parse("tel:" + phoneNo);
intent.setData(uri);
startActivity(intent);
break;
case R.id.btn_sms:
// 设置意图动作为发短信
intent.setAction(Intent.ACTION_SENDTO);
// 声明一个发送短信的Uri
Uri uri2 = Uri.parse("smsto:" + phoneNo);
intent.setData(uri2);
startActivity(intent);
break;
case R.id.btn_my:
intent.setAction("android.intent.action.NING");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
break;
}
}
}
隐式Intent还用到了过滤器的概念,把不符合匹配条件的过滤掉,剩下符合条件的按照优先顺序调用。
譬如创建一个App模块,AndroidManifest.xml里的intent-filter就是配置文件中的过滤器。像最常见的
首页活动MainAcitivity,它的activity节点下面便设置了action和category的过滤条件。其中
android.intent.action.MAIN表示App的入口动作,而android.intent.category.LAUNCHER表示在桌面
上显示App图标,配置样例如下:
过滤器
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity
Activity 消息传递
上一小节提到,Intent对象的setData方法只指定到达目标的路径,并非本次通信所携带的参数信息,真
正的参数信息存放在Extras中。Intent重载了很多种putExtra方法传递各种类型的参数,包括整型、双
精度型、字符串等基本数据类型,甚至Serializable这样的序列化结构。
只是调用putExtra方法显然不好管理,像送快递一样大小包裹随便扔,不但找起来不方便,丢了也难以知道。所以Android引入了Bundle概念,可以把Bundle理解为超市的寄包柜或快递收件柜,大小包裹由Bundle统一存取,方便又安全。
Bundle内部用于存放消息的数据结构是Map映射,既可添加或删除元素,还可判断元素是否存在。开发
者若要把Bundle数据全部打包好,只需调用一次意图对象的putExtras方法;若要把Bundle数据全部取
出来,也只需调用一次意图对象的getExtras方法。Bundle对象操作各类型数据的读写方法说明见表
接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹塞给意图对
象,再调用startActivity方法跳到意图指定的目标活动。完整的活动跳转代码示例如下:
public class ActSendActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tv_send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_send);
tv_send = findViewById(R.id.tv_send);
findViewById(R.id.btn_send).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, ActReceiveActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
}
}
前端
<?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">
<TextView
android:id="@+id/tv_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="今天的天气真不错" />
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="发送以上文字" />
</LinearLayout>
接受端代码
public class ActReceiveActivity extends AppCompatActivity {
private TextView tv_receive;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_receive);
tv_receive = findViewById(R.id.tv_receive);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
tv_receive.setText(desc);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
向上个Activity返回数据
数据传递经常是相互的,上一个页面不但把请求数据发送到下一个页面,有时候还要处理下一个页面的
应答数据,如果还要处理下一个页面的应答数据,此时就得分多步处理,详细步骤说明如下:
- 上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作,表示需要处理下一个页面的应答数据,该方法的第二个参数表示请求代码,它用于标识每个跳转的唯一性。
String request = "你吃饭了吗?来我家吃吧";
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, ActResponseActivity.class);
Bundle bundle = new Bundle(); // 创建一个新包裹
// 往包裹存入名为request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime());
// 往包裹存入名为request_content的字符串
bundle.putString("request_content", request);
intent.putExtras(bundle); // 把快递包裹塞给意图
// 期望接收下个页面的返回数据。第二个参数为本次请求代码
startActivityForResult(intent, 0);
- 下一个页面接收并解析请求数据,进行相应处理。接收代码示例如下
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
// 从包裹中取出名为request_time的字符串
String request_time = bundle.getString("request_time");
// 从包裹中取出名为request_content的字符串
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s",
request_time, request_content);
tv_request.setText(desc); // 把请求消息的详情显示在文本视图上
- 下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹。setResult方法的第一个参数表示应答代码(成功还是失败),第二个参数为携带包裹的意图对象。返回代码示例如下:
String response = "我吃过了,还是你来我家吃";
Intent intent = new Intent(); // 创建一个新意图
Bundle bundle = new Bundle(); // 创建一个新包裹
// 往包裹存入名为response_time的字符串
bundle.putString("response_time", DateUtil.getNowTime());
// 往包裹存入名为response_content的字符串
bundle.putString("response_content", response);
intent.putExtras(bundle); // 把快递包裹塞给意图
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
finish(); // 结束当前的活动页面
- 上一个页面重写方法onActivityResult,该方法的输入参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,结果代码用于判断下一个页面是否处理成功。如果下一个页面处理成功,再对返回数据解包操作,处理返回数据的代码示例如下
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{ // 接收返回数据
super.onActivityResult(requestCode, resultCode, intent);
// 意图非空,且请求代码为之前传的0,结果代码也为成功
if (intent!=null && requestCode==0 && resultCode== Activity.RESULT_OK) {
Bundle bundle = intent.getExtras(); // 从返回的意图中获取快递包裹
// 从包裹中取出名叫response_time的字符串
String response_time = bundle.getString("response_time");
// 从包裹中取出名叫response_content的字符串
String response_content = bundle.getString("response_content");
String desc = String.format("收到返回消息:\n应答时间为:%s\n应答内容为:%s",
response_time, response_content);
tv_response.setText(desc); // 把返回消息的详情显示在文本视图上
}
}
活动的附加信息
利用元数据传递配置信息
尽管资源文件能够配置字符串参数,然而有时候为安全起见,某个参数要给某个活动专用,并不希望其
他活动也能获取该参数,此时就不方便到处使用getString了。好在Activity提供了元数据(Metadata)
的概念,元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。
打开AndroidManifest.xml,在测试活动的activity节点内部添加meta-data标签,通过属性name指定元数据
的名称,通过属性value指定元数据的值。仍以天气为例,添加meta-data标签之后的activity节点如下所
示:
<activity android:name=".MetaDataActivity">
<meta-data android:name="weather" android:value="晴天" />
</activity>
元数据的value属性既可直接填字符串,也可引用strings.xml已定义的字符串资源,引用格式形如
“@string/字符串的资源名称”。下面便是采取引用方式的activity节点配置:
<activity android:name=".MetaDataActivity">
<meta-data
android:name="weather"
android:value="@string/weather_str" />
</activity>
配置好了activity节点的meta-data标签,再回到Java代码获取元数据信息,获取步骤分为下列3步:
- 调用getPackageManager方法获得当前应用的包管理器。
- 调用包管理器的getActivityInfo方法获得当前活动的信息对象。
- 活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。
public class MetaDataActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meta_data);
TextView tv_meta = findViewById(R.id.tv_meta);
// 获取应用包管理器
PackageManager pm = getPackageManager();
try {
// 从应用包管理器中获取当前的活动信息
ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
// 获取活动附加的元数据信息
Bundle bundle = info.metaData;
String weather = bundle.getString("weather");
tv_meta.setText(weather);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
给APP注册快捷方式
元数据不单单能传递简单的字符串参数,还能传送更复杂的资源数据,从Android 7.1开始新增的快捷方
式便用到了这点,譬如在手机桌面上长按支付宝图标,会弹出如图4-16所示的快捷菜单。
元数据的meta-data标签
除了前面说到的name属性和value属性,还拥有resource属性,该属性可指定一个XML文件,表示元数
据想要的复杂信息保存于XML数据之中。借助元数据以及指定的XML配置,方可完成快捷方式功能,具
体的实现过程说明如下:
首先打开res/values目录下的strings.xml,在resources节点内部添加下述的3组(每组两个,共6个)字
符串配置,每组都代表一个菜单项,每组又分为长名称和短名称,平时优先展示长名称,当长名称放不
下时才展示短名称。这3组6个字符串的配置定义示例如下:
<string name="first_short">first</string>
<string name="first_long">启停活动</string>
<string name="second_short">second</string>
<string name="second_long">来回跳转</string>
<string name="third_short">third</string>
<string name="third_long">登录返回</string>
接着在res目录下创建名为xml的文件夹,并在该文件夹创建shortcuts.xml,这个XML文件用来保存3组
菜单项的快捷方式定义,文件内容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="first"
android:shortcutLongLabel="@string/first_long"
android:shortcutShortLabel="@string/first_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.ActStartActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="second"
android:shortcutLongLabel="@string/second_long"
android:shortcutShortLabel="@string/second_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.JumpFirstActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="third"
android:shortcutLongLabel="@string/third_long"
android:shortcutShortLabel="@string/third_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.LoginInputActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>
由上述的XML例子中看到,每个shortcut节点都代表了一个菜单项,该节点的各属性说明如下:
- shortcutId:快捷方式的编号。
- enabled:是否启用快捷方式。true表示启用,false表示禁用。
- icon:快捷菜单左侧的图标。
- shortcutShortLabel:快捷菜单的短标签。
- shortcutLongLabel:快捷菜单的长标签。优先展示长标签的文本,长标签放不下时才展示短标签的文本。
以上的节点属性仅仅指明了每项菜单的基本规格,点击菜单项之后的跳转动作由shortcut内部的intent节点定义,该节点主要有targetPackage与targetClass两个属性需要修改,其中targetPackage属性固定为当前App的包名,而targetClass属性描述了菜单项对应的活动类完整路径。
然后打开AndroidManifest.xml
,找到MainActivity所在的activity节点,在该节点内部补充如下的元数据配置,其中name属性为android.app.shortcuts
,而resource属性为@xml/shortcuts:
这行元数据的作用,是告诉App首页有个快捷方式菜单,其资源内容参见位于xml目录下的
shortcuts.xml
。完整的activity节点配置示例如下:
<activity
android:name=".ActStartActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>