《第一行代码第二版》读书笔记 - 第二章 先从看得到的入手 - 探究活动

437 阅读7分钟

2.1 活动是什么

活动(Activity),是一种可以包含用户界面的组件,主要用户和用户进行交互,类似于 iOS 的 UIViewController。

2.2 活动的基本用法

在 Activity 中使用 Toast

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.first_layout);
    // 获取按钮对象并设置回调方法
    Button btn = findViewById(R.id.button_1);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(FirstActivity.this, "You clicked the button 1", Toast.LENGTH_SHORT).show();
        }
    });
}

在 Activity 中使用 Menu

首先,右击 res 中的 menu 目录 -> New -> Menu resource file , menu 文件命名为main。代码如下:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
    <item
        android:id="@+id/remove_item"
        android:title="Remove"/>
</menu>

然后,在 Activity 中重写 onCreateOptionsMenu 方法,可使用 control + o 快捷键。代码如下:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    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;    
}

onCreateOptionsMenu:创建 Menu 的重载方法 onOptionsItemSelected:Menu事件响应的重载方法

两者均可使用control + o ,然后输入关键字即可智能补全代码。

销毁一个 Activity

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        finish();
    }
});

该效果,和点击 Back 按钮一样。

2.3 活动之间的跳转 - 使用 intent

Intent 有意图、意向、目的之意,在 Android 中意味着活动跳转技术,类似于 iOS 中的 pushViewController。

两种 Intent 方式

  1. 显式 Intent
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivity(intent);
    }
});
  1. 隐式 Intent

在 AndroidManifest.xml 中配置如下:

<activity android:name=".SecondActivity" >
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

在 FirstActivity 中代码如下:

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent("com.example.activitytest.ACTION_START");
        startActivity(intent);
    }
});

需要注意每个 Intent 只能指定一个 action,但却能制定多个 category。

更多隐式 Intent 用法之

  1. 打开一个网页
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("http://www.baidu.com"));
        startActivity(intent);
    }
});
  1. 调用系统拨号界面
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(Intent.ACTION_DIAL);
        intent.setData(Uri.parse("tel:10086"));
        startActivity(intent);
    }
});

向下一个 Activity 传递数据

在第一个活动中用 putExtra() 放入数据

button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String data = "Hello SecondActivity";
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        intent.putExtra("extra_data", data);
        startActivity(intent);
    }
});

在第二个活动中用 getStringExtra() 获取数据

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        Intent intent = getIntent();
        String data = intent.getStringExtra("extra_data");
        Log.d("SecondActivity", data);
    }

}

返回数给上一个 Activity

  1. 在第一个活动中用 startActivityForResult 表示关注返回结果
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivityForResult(intent, 1);
    }
});
  1. 在第二个活动中用 setResult() 返回值。除了在按钮事件中返回之外,还可以在 Back 键返回时(onBackPressed())
button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("data_return", "Hello FirstActivity");
                setResult(RESULT_OK, intent);
                finish();
            }
        });
    }s
  1. 在第一个活动中,重写 onActivityResult() 接收数据
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case 1:
            if (resultCode == RESULT_OK) {
                String returnedData = data.getStringExtra("data_return");
                Log.d("FirstActivity", returnedData);
            }
            break;
        default:
    }
}

2.4 活动的生命周期

Android 使用 任务(Task)管理活动,一个任务就是一组存放在栈里的活动的集合,这个栈叫做返回栈(Back Stack)。

活动状态

  1. 运行状态

    活动位于栈顶时,处于运行状态。系统不会回收该活动

  2. 暂停状态

    活动不再处于栈顶,但仍然可见时,处于暂停状态。系统不会轻易回收该活动,除非内存极低。

  3. 停止状态

    活动不再处于栈顶,且完全不可见时,处于停止状态。系统会为这种活动保存相应的状态和成员,但并不完全可靠,当其它地方需要内存时,停止状态的活动可能被回收。

  4. 销毁状态

    活动从返回栈移除后,变成了销毁状态。系统会倾向于回收该活动

活动的生存期

Activity 类中定义了 7 个回调方法,覆盖活动生命周期的每一个环节:

  1. onCreate()

    在活动“第一次被创建时”调用。应在该方法中完成活动的初始化操作如:加载布局、绑定事件等

  2. onStart()

    在活动“由不可见变为可见时”调用,类似于 iOS 控制器的 viewDidAppear

  3. onResume()

    在活动“准备好和用户交互时”调用。此时活动一定处于栈顶,且处于运行状态

  4. onPause()

    在系统“准备去启动或恢复另一个活动时”调用,等同于 viewWillDisappear。通常会在该方法中将一些消耗 CPU 的资源释放掉,及保存一些关键数据(但该方法执行速度要快,不能影响到新的栈顶活动的使用)

  5. onStop()

    在活动“完全不可见时”调用,等同于 viewDidDisappear。和 onPause 方法 主要区别是:如果启动的活动是一个对话框式活动,那么 onPause 方法会执行,onStop 不会执行

  6. onDestory()

    在“活动被销毁之前”调用,等同于 dealloc。之后活动变为销毁状态

  7. onRestart()

    在活动“由停止状态变为运行状态之前”调用。

以上 7 个方法出 onRestart 之外,两两相对,从而可将活动分为 3 中生存期:

  1. 完整生存期

    onCreate 和 onDestory 之间是完整生存期。通常,一个活动在 onCreate 中完成各种初始化操作,在 onDestory 中完成释放内存操作

  2. 可见生存期

    onStart 和 onStop 之间是可见生存期。在此期间,活动对于用户总是可见的,即便有可能无法和用户进行交互。可通过这两个方法,管理那些对用户可见的资源。比如,在 onStart 中加载资源,在 onStop 中对资源进行释放,以保证处于停止的活动不会占用过多内存

  3. 前台生存期

    onResume 和 onPause 之间是前台生存期。 此时活动处于运行状态可以和用户交互。

案例体会活动生命周期

相关代码

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "onCreate");

        // 1. dialog activity button config
        Button startDialogActivity = findViewById(R.id.start_dialog_activity);
        startDialogActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, DialogActivity.class);
                startActivity(intent);
            }
        });
        // 2. normal avtivity button config
        Button startNormalActivity = findViewById(R.id.start_normal_activity);
        startNormalActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, NormalActivity.class);
                startActivity(intent);
            }
        });
    }


    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}
  1. 运行工程后的界面

    日志打印:

  2. 点击 Start NormalActivity

    日志打印:

  3. 点击 Back ,回到首页的日志打印:

  4. 点击 Start DialogActivity

    日志打印:
    只有 onPause 打印,是因为 dialog 没有完全遮挡 MainActivity。所以只进入了暂停状态,没有进入停止状态

  5. 点击 Back,回到首页

  6. 在首页,点击 Back

  7. 再次回到 App

活动被回收的相关处理

代码示例较清晰

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key", tempData);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }
    ...
}

2.5 活动的启动模式

先看启动模式的设置

<activity
    android:name=".FirstActivity"
    android:launchMode="singleTop"
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

android:launchMode 的值有四种:

  1. standard:

    默认的启动模式,可打开多个处于栈顶的同类活动实例

  2. singleTop:

    在启动活动时,如果发现返回栈的栈顶已是该活动,则认为可直接使用它,不会再创建新的活动实例。若未在栈顶,则依旧会创建多个实例。

  3. singleTask:

    在整个应用程序的上下文,只存在一个实例。

  4. singleInstance:

    该模式会启用一个新的返回栈来管理这个活动。

2.6 活动的最佳实践

一些常用的开发技巧

知晓当前在哪个活动

定义一个 BaseActivity ,让所有活动继承自该类

public class BaseActivity extends AppCompatActivity {
@Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("BaseActivity", getClass().getSimpleName());
    }
}

进入一个活动后,便会打印当前活动名称

随时随地退出程序

  1. 新增 ActivityCollector 类
public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity) {
       activities.add(activity);
    }

    public static void removeActivity(Activity activity) {
      activities.remove(activity);
    }

    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
            activity.finish();
            }
        }
        activities.clear();
        // 杀掉当前进程
        android.os.Process.killProcess(android.os.Process.myPid());

    }
    
}
  1. 改造 BaseActivity
public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

}
  1. 在某一活动中,退出程序
public class ThirdActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("ThirdActivity", "Task id is " + getTaskId());
        setContentView(R.layout.third_layout);
        
        // 按钮,用以测试退出程序
        Button button3 = (Button) findViewById(R.id.button_3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityCollector.finishAll();
            }
         });

    }
}

启动活动最佳实践

简言之,启动活动的代码应写在需要启动的活动中。譬如需要启动 SecondActivity:

public class SecondActivity extends BaseActivity {

    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);
    }
    ...
}

需要打开该活动,则调用该方法即可

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
    }
});