安卓开发1

安卓开发四大组件比较重要。其他的都相当于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>

posted @ 2023-04-19 12:48  ZZX11  阅读(43)  评论(0编辑  收藏  举报