「android」 新建活动

Posted by Dawn-K's Blog on January 4, 2021

新建活动

[toc]

初始化

新建一个 ActivetyTest 选择 no activity

com.example.activitytest 中新建一个空的活动, 不要勾选 generate Layout File 和 launcher Activity

app/src/main/res/ 下新建 layout 文件夹, 在次文件夹下, 新建 layout resource file , 命名为 first_layout, 根元素默认选择为LinearLayout .

绘制布局一个按钮

打开刚才新建的 first_layout.xml 文件, 切换到text视图. 然后修改如下

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button_1"
        android:text="Button 1"
        />
</LinearLayout>

通过design视图可以看到, 这样就创建了一个新的按钮, 它位于屏幕顶端, 与屏幕一样宽, 高度取决于其内容. 其内容为”Button 1” , id的设置是指新定义了它的id.

加载布局

打开 FirstActivity.java

1
2
3
4
5
6
7
8
9
10
11
public class FirstActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//    项目中添加任何资源都会在R文件中生成一个相应的资源id
//    R.layout.first_layout 可获取到first_layout.xml 的布局id
//    添加下面这一行,将这个布局加载到活动中,传入的参数是布局的id
        setContentView(R.layout.first_layout);
    }
}

在 AndroidManifest 中注册

实际上 Android Studio 已经给注册完了. 也就是 <actvity> 标签

但是我们还要将其设置为主活动. 也就是运行程序时首先启动的活动.

其实程序没有主活动也可以, 这样它就无法被主动启动, 只能作为第三方服务在其他应用内部调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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/AppTheme">
        <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>
        </activity>
    </application>
</manifest>

文件编写时一定一定要仔细, 尤其是activity标签内还有添加东西.

运行程序

运行成功之后, 会发现程序的左上角是 android:lable 的内容, 并且是启动器(可以理解为桌面)中应用程序显示的名称, 但是如果点开应用的信息的话会发现它是工程的名字.

Toast

Toast 就是安卓在下方的通知, 停留一段时间自动消失 在 FirstActivity.java 下添加监听器.

1
2
3
4
5
6
7
8
9
10
11
12
/* 设置按钮的点击事件 */
// findViewById 可以找到布局文件中定义的元素,传入xml中定义的id,返回view对象
Button button1 = (Button) findViewById(R.id.button_1);
// 注册监听器, 点击按钮时会执行onClick方法
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 通过静态方法创建toast对象,然后使用show,显示这个提示
        // 第一个参数Context 上下文,此处传入活动, 第二个参数文本内容,第三个是时长,自带了short和long
        Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_SHORT).show();
    }
});

创建资源文件

创建 res/menu 文件夹, 创建 Menu Resource File 文件, 取名为main. 就得到了main.xml

修改此文件如下:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!--    item 用以创建菜单项, id是标识符  title是显示名称-->
    <item
        android:id="@+id/add_item"
        android:title="Add" />
    <item
        android:id="@+id/remove_item"
        android:title="Remove" />
</menu>

这时候可以从design视图看到右上角多了菜单, 包含刚才创建的两项

重载菜单显示

在 FirstActivity.java 类中重载菜单显示函数

按ctrl+O可以快速重载

1
2
3
4
5
6
7
8
9
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // 给当前活动创建菜单
        // 第一个参数是指定创建菜单的资源文件也就是刚才创建的res/menu/main.xml
        // 第二个参数是将菜单添加到哪个对象中,这个是传入的参数
        getMenuInflater().inflate(R.menu.main, menu);
        // 允许菜单显示,返回true
        return true;
    }

重载菜单选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        // 通过传入的item的id号,执行不同的动作
        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:
        }
        return true;
    }

运行效果

运行虚拟机, 发现右上角有三个点, 然后点击之后弹出 Add Remove 框, 点击之后有文字提示. 与onOptionsItemSelected中的一致.

Intent

intent 是一种能够切换活动的方法.

显式Intent

修改 FirstActivity.java 中的 onCreate() 中的按钮监听器, 添加如下内容.

1
2
3
// 显式Intent
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);

隐式intent

创建 ` SecondActivity.java , 活动选择为 Empty Activity , 配置文件为 second_layout.xml` . 在 AndroidManifest.xml 下将其对应的部分修改如下.

1
2
3
4
5
6
7
8
9
10
11
<activity android:name=".SecondActivity">
    <!-- 指定当前活动能够响应的action和category -->
    <!-- action只能有一个,category可以有多个,当且仅当action和所有category都对上的时候,才能判定为可响应 -->
    <!-- 若一个Intent没有任何可响应它的活动.则会导致程序崩溃 -->
    <!-- 只有action和category中的内容能够同时匹配上Intent中指定的时,这个活动才能响应这个Intent -->
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.example.activitytest.MY_CATEGORY" />
    </intent-filter>
</activity>

在 FirstActivity.java的监听器中加入如下内容(记得将刚才测试显式intent的代码注释掉)

1
2
3
4
// 若想要启动的活动的catalog是DEFAULT的话,则无需指定intent的catalog,startActivity会自动给他加上
// 隐式Intent ,通过action 和 catelog 才决定启动哪个活动,可广泛用于调用其他程序的活动
Intent intent = new Intent("com.example.activitytest.ACTION_START");
intent.addCategory("com.example.activitytest.MY_CATEGORY");

更多用法

调用浏览器

修改 FirstActivity.java 的监听器如下

1
2
3
4
5
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
// 不加下面这行无法调用ThirdActivity
intent.addCategory("android.intent.category.BROWSABLE");
startActivity(intent);

此时启动AVD, 会发现点击按钮之后, 浏览器(chrome)会自动被调用.

其中setData就意味着需要给 intent-filter 增加data标签, 可以用于指定 scheme(协议) host, port path mineType(数据类型)

创建 ` ThirdActivity.java , 活动选择为 Empty Activity , 配置文件为 third_layout.xml` . 在 AndroidManifest.xml 下将其对应的部分修改如下.

1
2
3
4
5
6
7
8
9
10
11
12
<activity android:name=".ThirdActivity">
<!-- 添加完下面的intent-filter 之后,就可在手机的浏览器默认应用选择里发现 ActivityTest 这个软件,选中之后,待会运行就是-->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <!-- 此处与书上不一致,必须增加这行才可编译通过 -->
        <category android:name="android.intent.category.BROWSABLE"/>

        <data android:scheme="http" />
    </intent-filter>
</activity>

拨号

修改监听器如下

1
2
3
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

启动AVD, 点击按钮就可以调出拨号界面.

通过Intent向其他活动传递消息

单向传递

修改FirstActivity 里面的监听器如下

1
2
3
4
5
6
7
// 向second传递消息
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// 第一个参数是tag,第二个元素是携带的信息,这两个可以理解为键值对,待会取信息的时候也要用键取值
intent.putExtra("extra_data", data);

startActivity(intent);

在 SecondActivity 的 onCreate 里面加入如下内容便可读取. 也就是可将intent看做类似字典的逻辑结构.

1
2
3
4
// 接受来自FirstActivity的消息
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity", data);
双向发送消息

修改 FirstActivity 的监听器, 去掉 startActivity , 修改为

1
startActivityForResult(intent, 1);

此处的1就是给这个intent的一标记, 用于有多个活动回调 FirstActivity时进行辨别.

修改 SecondActivity 的监听器如下

1
2
3
4
5
6
// 构造返回信息的内容
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
// 此处设置 Result的状态,一般就ok和cancel两种
setResult(RESULT_OK, intent);
finish();

然后重载 onBackPress(), 内容和onClick()的相同, 用于在使用返回键返回 FirstActivity 时能够返回同样的结果.

最后在 FirstActivity 中重载 onActivityResult, 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    // 书上没有这个super的调用,但是没有就编译不过
    super.onActivityResult(requestCode, resultCode, data);
    // switch 用来判断此回调是来自哪个活动的,此处的1就是刚才 startActivityForResult里面定义的
    switch (requestCode) {
        case 1:
            if (resultCode == RESULT_OK) {
                String returnedData = data.getStringExtra("data_return");
                Log.d("FirstActivity", returnedData);
            }
            break;
        default:
    }
}

运行程序, 就可以发现在 SecondActivity 无论是通过按钮还是返回键返回, 都可以在Logcat中发现打印的信息了.

但注意在 SecondActivity 的 onBackPress 中不能调用super, 否则后续的不会执行.

活动生命周期

  运行状态 暂停状态 停止状态 销毁状态
在栈顶? x x x
可见? x x
未被销毁? x

可见可以有部分可见.

onCreate 活动第一次创建 onStart 第一次创建时, 停止状态->运行状态 onStop 运行状态->停止状态, 销毁 onRestart 停止状态->运行状态 onPause 运行状态->暂停状态, 运行状态->停止状态, 销毁 onResume 第一次创建时, 暂停状态->运行状态, 停止状态->运行状态 onDestory 销毁

可见, onCreate 和 onDestory 是反义词, 只会在 产生/销毁 时调用一次. onStart 和 onStop 是反义词, 只会在 产生/销毁 运行/停止 时调用 onResume 和 onPause 是反义词, 只会在 产生/销毁 运行/停止 运行/暂停 时调用 onRestart 相比于 onStart , 不会在创建时调用, 只会在 停止->运行时调用

启动模式

重新打开 ActivityTest 项目

standard

此模式 只要调用新活动就一定新建, 即使已经存在于栈中. 让 FirstActivity 的按钮调用自身, 同时打印日志. 启动项目, 点击按钮两次, 从日志中发现创建了两个活动. 而且需要三次back才能退出.

1
2
3
// 尝试去调用自己,用以验证 standard启动模式
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
startActivity(intent);

singleTop

此模式, 在栈顶活动调用自身时不会新建, 但其他情况下仍然会. 比如栈可能 FirstActivity , SecondActivity , FirstActivity 这种情况

singleTask

singleTask 会在启动新活动时先检查栈中有无活动, 如果有, 就找到它, 并且弹出它上方所有活动.

此情况可通过 FirstActivity重写 onRestart , SecondActivity重写 onDestory 来验证.

singleInstance

若某个活动的启动模式被设置为 singleInstance, 那么, 它在被调用时, 会另开一个栈.

我们令 SecondActivity 为此模式. 然后让 FirstActivity 调用 SecondActivity , SecondActivity 调用 ThirdActivity.

在各自的 onCreate 中调用

1
2
// 以FirstActivity举例
Log.d("FirstActivity", "Task id is " + getTaskId());

运行程序, 可发现 FirstActivity 与 ThirdActivity 处于同一个栈中, SecondActivity单独位于一个栈. 且返回的顺序是 ThirdActivity -> FirstActivity -> SecondActivity. 这是因为返回的顺序是先将当前栈一个一个弹空, 再切换到别的栈再弹出.

最佳实践

查看当前的活动

直接创建java类 BaseActivity(不要用创建活动的方式, 直接创建类即可).

使其继承 AppCompatActivity, 然后令之前的三个活动全部放弃原有的继承, 转而继承 BaseActivity.

重写 BaseActivity的onCreate, 写入如下语句

1
Log.d("BaseActivity", getClass().getSimpleName());

然后启动程序, 观察日志输出

1
2
3
BaseActivity: FirstActivity
BaseActivity: SecondActivity
BaseActivity: ThirdActivity

这样就可以实时看到当前是哪个活动了.

启动活动的最佳方法

其实就是多加一个 actionStart() 函数 以修改 SecondActivity 的启动为例, 加入如下代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
    /*
     * 调用方法
     * 比如在FirstActivity中想要启动 SecondActivity
     * 则使用如下语句
     * SecondActivity.actionStart(FirstActivity.this,"data1","data2");
     * */
    public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }

按钮的点击事件

除了之前提到过的按钮加监听器的方式之外,还有另一种方式.

将活动类设置为实现View. OnClickListener 接口. 然后在onCreate中将此按钮加入this监听器. 然后重载 onClick方法, 在方法中用switch来指定每个按钮被点击后的行为.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DicomViewer extends Activity implements View.OnClickListener {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 给按钮加入监听器,监听器是类本身
        Button button_next = (Button) findViewById(R.id.button_next);
        button_next.setOnClickListener(this);
    }

    // 指定各个按钮被点击后的行为
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_next:
                Log.d("button_next", "button_next clicked");
                next(v);
                break;
            default:
        }
    }
}