第4章 Activity活动

Mr.Tong...
  • Android
  • Android
大约 15 分钟

第 4 章 Activity活动

📖4.1 启停活动页面

✅Activity的启动和结束

从当前页面跳到新页面,跳转代码如下

  • startActivity(new Intent(源页面.this,目标页面.class))

从当前页面回到上一个页面,相当于关闭当前页面,返回代码如下

  • finish(); // 结束当前的活动页面

案例:

helloword页面跳转到第二页面

image-20230903171831297

①xml中定义组件

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_toStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="跳到下一个页面"/>

②Java逻辑代码编写,startActivity(new Intent(源页面.this,目标页面.class))实现

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button bt_toStart = findViewById(R.id.bt_toStart);
        bt_toStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this,finshActivity.class));
            }
        });

从第二页面返回

image-20230903172045944 ①xml定义组件

    <ImageView
        android:id="@+id/img_back"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/icon_back"/>
    <Button
        android:id="@+id/bt_fish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="完成"/>

②java逻辑代码,调用 finish();

public class finshActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_finsh);
        ImageView img_back = findViewById(R.id.img_back);
        Button bt_fish = findViewById(R.id.bt_fish);
        img_back.setOnClickListener(this);
        bt_fish.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.img_back || v.getId() == R.id.bt_fish){
             finish();
        }
    }
}

✅ Activity的生命周期

image-20230903172714197

  • onCreate: 创建活动。把页面布局加载进内存,进入了初始状态。
  • onStart: 开始活动。把活动页面显示在屏幕上,进入了就绪状态
  • onResume: 恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等等
  • onPause: 暂停活动。页面进入暂停状态,无法与用户正常交互
  • onStop: 停止活动。页面将不在屏幕上显示。
  • onDestroy: 销毁活动。回收活动占用的系统资源,把页面从内存中清除。
  • onRestart:重启活动。重新加载内存中的页面数据。
  • onNewIntent:重用已有的活动实例。

上述的生命周期方法,涉及复杂的App运行状态,更直观的活动状态切换过程如图4-2所示。

image-20230903174547020

如果一个Activity已经启动过,并且存在当前应用的Activity任务栈中,启动模式为singleTask,singlelnstance或singleTop(此时已在任务栈顶端),那么在此启动或回到这个Activity的时候,不会创建新的实例,也就是不会执行onCreate方法,而是执行onNewIntent

看如下例子:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "ning";
    //创建活动
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"MainActivity onCreate");
        setContentView(R.layout.activity_main);
        Button bt_toStart = findViewById(R.id.bt_toStart);
        bt_toStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this,finshActivity.class));
            }
        });
    }
    //开始活动
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"MainActivity onStart");
    }
    //恢复活动
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"MainActivity onResume");
    }
    //暂停活动
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"MainActivity onPause");
    }
    //停止活动
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"MainActivity onStop");
    }
    //重启活动
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"MainActivity onRestart");

    }
    //销毁活动
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"MainActivity onDestroy");
    }

}

①当启动activity页面时,执行onCreate、onStart、onResume

image-20230903183331632

②当跳转其他activity时,执行onPause、onStop

image-20230903183630345

③当从其他activity返回之前的activity时,执行onRestart、onStart、onResume

image-20230903183818235

④当跳转其他activity,但还未加载要跳转的activity时马上返回时,执行onPause、onResume

image-20230903184028048

⑤当前activity的进程被关闭后,重新再打开当前activity时,会重新执行onCreate、onStart、onResume

image-20230903184501165

✅Activity的启动模式

通过启动模式控制Activity的出入栈行为。不同的启动模式可能会影响Activity的生命周期和任务栈的管理方式,看如下例子:

假定某个App分配到的活动栈大小为 3 ,该App先后打开两个活动,此时活动栈的变动情况如图4-7所示。

Image From 笔记-Android 开发从入门到实战

然后按下返回键,依次结束已打开的两个活动,此时活动栈的变动情况如图4-8所示。

Image From 笔记-Android 开发从入门到实战

结合图4-7与图4-8的入栈与出栈流程,即可验证结束活动之时的返回逻辑了。

配置文件中静态指定启动模式

打开AndroidManifest.xml,给activity节点添加属性android:launchMode,属性值填入standard表示采取标准模式,当然不添加属性的话默认就是标准模式。具体的activity节点配置内容示例如下:

<activity android:name=".JumpFirstActivity" android:launchMode="standard" />

1. 默认启动模式 standard

启动的 Activity 会依照启动顺序被依次压入 Task 栈中。

image-20230908135614385

2. 栈顶复用模式 singleTop

在该模式下,如果栈顶 Activity 为我们要新建的 Activity(目标Activity),那么就不会重复创建新的Activity

image-20230908135705013

应用场景适合开启渠道多、多应用开启调用的 Activity,通过这种设置可以避免已经创建过的 Activity 被重复创建,多数通过动态设置使用。例如微信支付宝支付界面。

3. 栈内复用模式 singleTask

如果task 栈内存在目标 Activity 实例,则将 task 内的对应 ·实例之上的所有 Activity 弹出栈,并将对应 Activity 置于栈顶,获得焦点。

image-20230908135904366

应用场景

  • 程序主界面 :我们肯定不希望主界面被创建多次,而且在主界面退出的时候退出整个 App 是最好的效果。
  • 耗费系统资源的Activity :对于那些及其耗费系统资源的 Activity,我们可以考虑将其设为 singleTask模式,减少资源耗费。

4. 全局唯一模式 singleInstance

在该模式下,我们会为目标 Activity 创建一个新的 Task 栈,将目标 Activity 放入新的 Task,并让目标Activity获得焦点。新的 Task 有且只有这一个 Activity 实例。 如果已经创建过目标 Activity 实例,则不会创建新的 Task,而是将以前创建过的 Activity 唤醒。

image-20230908141607517

5. 动态设置启启动模式

AndroidManifest.xml中通过 launchMode 属性设置的,这个被称为静态设置,动态设置是通过 Java 代码设置的。通过 Intentintent.setFlags()动态设置 Activity启动模式

//比如
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP );
//比如,常用登录成功后不再返回登录页面
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
  • Intent.FLAG_ACTIVITY_NEW_TASK: 开辟一个新的任务栈

  • Intent.FLAG_ACTIVITY_SINGLE_TOP:当顶为待跳转的活动实例之时,则重用栈顶的实例

  • Intent.FLAG_ACTIVITY_CLEAR_TOP: 当中存在待跳转的活动实例时,则重新创建一个新实例,并清除原实例上方的所有实例

  • Intent.FLAG_ACTIVITY_NO_HISTORY: 栈中不保存新启动的活动实例

  • Intent.FLAG_ACTIVITY_CLEAR_TASK: 跳转到新页面时,栈中的原有实例都被清空

Image From 笔记-Android 开发从入门到实战

📖4.2 在活动之间传递消息

✅ 显式Intent和隐式Intent

Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:

  • 标明本次通信请求从哪里来、到哪里去、要怎么走
  • 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据
  • 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容

image-20230908143856647

🔋显式Intent,直接指定来源活动与目标活动,属于精确匹配。它有三种构建方式:

  • Intent的构造函数中指定。
  • 调用意图对象的setClass方法指定
  • 调用意图对象的setComponent方法指定
//构造函数中指定
Intent intent = new Intent(this, ActNextActivity.class);  

//setClass方法指定
Intent intent = new Intent();  
intent.setClass(this, ActNextActivity.class);

//setComponent方法指定
Intent intent = new Intent(); 
ComponentName component = new ComponentName(this, ActNextActivity.class); 
intent.setComponent(component);  

🔋隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配

通常App不希望向外部暴露活动名称,只给出一个事先定义好的标记串,这样大家约定俗成、按图索骥就好,隐式Intent便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。常见系统动作的取值说明见表4-4。

image-20230908145606628

调用系统拨号程序的代码例子:

String phoneNo = "12345";
Intent intent = new Intent();  // 创建一个新意图
intent.setAction(Intent.ACTION_DIAL);  // 设置意图动作为准备拨号 
Uri uri = Uri.parse("tel:" + phoneNo);  // 声明一个拨号的Uri 
intent.setData(uri);  // 设置意图前往的路径
startActivity(intent);  // 启动意图通往的活动页面

隐式Intent还用到了过滤器的概念,把不符合匹配条件的过滤掉,剩下符合条件的按照优先顺序调用。

比如创建一个App模块,AndroidManifest.xml里的intent-filter就是配置文件中的过滤器。

像最常见的首页活动MainAcitivity,它的activity节点下面便设置了actioncategory的过滤条件。其中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发送数据

📚Bundle

在代码中发送消息包裹,调用意图对象的putExtras方法,即可存入消息包裹在代码中接收消息包裹,调用意图对象的getExtras方法,即可取出消息包裹。 Bundle对象操作各类型数据的读写方法说明见下表。

image-20230908152451036

案例:SendActivity发送信息,ReceiveActivity接收信息

image-20230909141915584

数据使用Bundle进行封装

SendActivity.java

// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, ActReceiveActivity.class); 
Bundle bundle = new Bundle();  // 创建一个新包裹
// 往包裹存入名为request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime()); 
// 往包裹存入名为request_content的字符串
bundle.putString("request_content", tv_send.getText().toString()); 
intent.putExtras(bundle);  // 把快递包裹塞给意图
startActivity(intent);  // 跳转到意图指定的活动页面

ReceiveActivity.java

// 从布局文件中获取名为tv_receive的文本视图
TextView tv_receive = findViewById(R.id.tv_receive); 
// 从上一个页面传来的意图中获取快递包裹
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_receive.setText(desc);  // 把请求消息的详情显示在文本视图上

✅向上一个Activity返回数据

案例:ReceiveActivityResponseActivity发送信息,ResponseActivity收到后返回回应信息

image-20230909142739040

第一步:ReceiveActivityResponseActivity发送信息

public class RequestActivity extends AppCompatActivity implements View.OnClickListener {
    String request = "你吃饭了吗?来我家吃";
    TextView tv_request;
    TextView tv_result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_request);
        tv_request = findViewById(R.id.tv_request);
        tv_request.setText("待发送数据:"+request);
        //发送信息
        findViewById(R.id.bt_request).setOnClickListener(this);

        //返回响应数据
        tv_result = findViewById(R.id.tv_result);

    }

    //发送信息
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,ResponseActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_content",request);
        intent.putExtras(bundle);
        //通过startActivityForResult实现接受回调信息
        startActivityForResult(intent,0);

    }
    //接收回调响应信息
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data != null && requestCode == 0 && resultCode == Activity.RESULT_OK){
            Bundle bundle = data.getExtras();
            String responseTime = bundle.getString("response_time");
            String responseContent = bundle.getString("response_content");
            String desc = String.format("收到返回消息:\n应答时间为:%s\n应答内容为:%s", responseTime, responseContent);
            tv_result.setText(desc);
        }
    }
}

第二步:ResponseActivity接收信息并返回回应信息

public class ResponseActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_response);
        //接受信息
        Bundle bundle = getIntent().getExtras();
        String requestTime = bundle.getString("request_time");
        String requestContent = bundle.getString("request_content");
        String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s",requestTime,requestContent);
        TextView tv_response = findViewById(R.id.tv_response);
        tv_response.setText(desc);

        //回复
        findViewById(R.id.bt_response).setOnClickListener(this);

    }
    //回复点击事件
    @Override
    public void onClick(View v) {
        String response = "我吃过了,还是你来我家吃";
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString("response_time", DateUtil.getNowTime());
        bundle.putString("response_content",response);
        intent.putExtras(bundle);
        setResult(Activity.RESULT_OK,intent);
        //结束当前活动
        finish();
    }

}

第三步:ReceiveActivity重写onActivityResult方法,进行接收ResponseActivity的回调信息,代码在第一步。

细节:

  • 发送时使用 startActivityForResult(intent,0);,两个参数一个意图intent,一个requestCode
  • 接收时使用getIntent().getExtras()来获取参数
  • 返回信息要使用 setResult(Activity.RESULT_OK,intent);,参数是resultCode和意图intent
  • 接收响应数据要重写onActivityResult()方法

①startActivityForResult的替代

api29中提示已过时,通过如下图官方描述得知,startActivityForResult已经过时,继而推出了名为Activity Result API的组件。官方建议使用registerForActivityResult

image-20230909152302036

改进:主要是ReceiveActivity有变化,将startActivityForResult换成了registerForActivityResult

public class RequestActivity extends AppCompatActivity implements View.OnClickListener {
    String request = "你吃饭了吗?来我家吃";
    TextView tv_request;
    TextView tv_result;
    ActivityResultLauncher<Intent> register;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_request);
        tv_request = findViewById(R.id.tv_request);
        tv_request.setText("待发送数据:"+request);
        //发送信息
        findViewById(R.id.bt_request).setOnClickListener(this);
        //返回响应数据
        tv_result = findViewById(R.id.tv_result);
        
        //接收回调响应信息
        register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result != null){
                Intent intent = result.getData();
                if (intent != null && result.getResultCode() == Activity.RESULT_OK){
                    Bundle bundle = intent.getExtras();
                    String responseTime = bundle.getString("response_time");
                    String responseContent = bundle.getString("response_content");
                    String desc = String.format("收到返回信息:\n应答时间:%s\n应答内容;%s",responseTime,responseContent);
                    tv_result.setText(desc);
                }
            }
        });

    }

    //发送信息
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this,ResponseActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_content",request);
        intent.putExtras(bundle);
        //通过startActivityForResult实现接受回调信息
        register.launch(intent);
    }


}

对比registerForActivityResult有什么不同

  • 不需要重写onActivityResult
  • 使用 register.launch(intent)发送请求数据,不需要携带requestCode请求码。

为什么startActivityForResult过时使用registerForActivityResult替代,它有啥缺点?

  • 它需要在onActivityResult()方法中处理返回数据,这样会导致代码分散和难以维护
  • 它需要使用请求码和结果码来区分不同的请求和结果,这样会增加出错的可能性
  • 它不能很好地适应新的生命周期模式,比如Jetpack中的ViewModel和LiveData

因此,官方推荐使用Activity Result API来替代startActivityForResult()方法,它有以下优点:

  • 它可以将请求和结果封装在一个对象中,避免了请求码和结果码的使用。
  • 它可以将返回数据的处理放在一个回调函数中,避免了代码分散和难以维护。
  • 它可以很好地适应新的生命周期模式,比如Jetpack中的ViewModel和LiveData

📖4.3 为活动补充附加信息

✅利用资源文件配置字符串

利用Bundle固然能在页面跳转的时候传送数据,但这仅限于在代码中传递参数,如果要求临时修改某个参数的数值,就得去改Java代码。然而直接修改Java代码有两个弊端:

  • 代码文件那么多,每个文件又有许多行代码,一下子还真不容易找到修改的地方。
  • 每次改动代码都得重新编译,让Android Studio编译的功夫也稍微费点时间。

有鉴于此,对于可能手工变动的参数,通常把参数名称与参数值的对应关系写入配置文件,由程序在运行时读取配置文件,这样只需修改配置文件就能改变对应数据了。res\values目录下面的strings.xml就用来配置字符串形式的参数,打开该文件,发现里面已经存在名为app_name的字符串参数,它配置的是当前模块的应用名称。

看如下例子:

第一步:在strings.xml中定义:

<string name="weather_str">晴天</string>

第二步:打开活动页面的Java代码,调用getString方法即可根据“R.string.参数名称”获得指定参数的字符串值。

// 显示字符串资源
private void showStringResource() {
    // 从strings.xml获取名叫 weather_str的字符串值a
   String value = getString(R.string.weather_str); 
     // 在文本视图上显示文字 
   tv_resource.setText("来自字符串资源:今天的天气是"+value);
}

✅利用元数据传递配置信息

元数据定义在AndroidManifest.xml文件中,以天气为例,添加meta-data标签之后的activity节点如下所示:

<activity android:name=".MetaDataActivity">
   <meta-data android:name="weather" android:value="晴天" /> 
</activity>

元数据的value属性既可直接填字符串,也可引用strings.xml已定义的字符串资源,如下引用strings.xml

<activity android:name=".MetaDataActivity"> 
   <meta-data
              android:name="weather"
              android:value="@string/weather_str" /> 
</activity>

配置好了activity节点的meta-data标签,再回到Java代码获取元数据信息,获取步骤分为下列 3 步:

  • 调用getPackageManager方法获得当前应用的包管理器。
  • 调用包管理器的getActivityInfo方法获得当前活动的信息对象。

活动信息对象的metaDataBundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。

把上述 3 个步骤串起来,得到以下的元数据获取代码:

// 显示配置的元数据
private void showMetaData() { 
   try {
       PackageManager pm = getPackageManager(); // 获取应用包管理器 
       // 从应用包管理器中获取当前的活动信息
       ActivityInfo act = pm.getActivityInfo(getComponentName(),PackageManager.GET_META_DATA);
       Bundle bundle = act.metaData; // 获取活动附加的元数据信息
       String value = bundle.getString("weather"); // 从包裹中取出名叫weather的字符串
       tv_meta.setText("来自元数据信息:今天的天气是"+value); // 在文本视图上显示文字 
  } catch (Exception e) {
       e.printStackTrace(); 
 }
}

然后在onCreate方法中调用showMetaData方法,重新运行App观察到的界面如图4-15所示,可见成功获得AndroidManifest.xml配置的元数据。

✅给应用页面注册快捷方式

你认为这篇文章怎么样?

  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.1