第一行代码第二版(郭霖著)笔记之第二章(探究活动)

来源:互联网 发布:json文件修改器 编辑:程序博客网 时间:2024/06/05 06:32

1. 在活动中使用menu

由于手机屏幕空间有限,当你的活动中有大量的菜单需要显示的时候,充分利用屏幕空间就显得格外重要。

Android提供的菜单功能既能让大量菜单得到展示,同时还不占用任何屏幕空间。

  • 在res文件夹下创建名为menu的文件夹,文件夹里面创建名为main.xml的文件,内容如下:
<?xml version="1.0" encoding="utf-8"?><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>

说明:创建两个菜单项,其中标签就是用来创建具体的某一个菜单项,然后通过android:id给这个菜单项指定一个唯一的标识符,通过android:title给这个菜单项指定一个名称。

  • 在MainActivity里面重写onCreateOptionsMenu和onOptionsItemSelected方法,前者用于创建菜单页面,后者用于实现菜单的点击。
@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;        }        return true;    }

说明:

  1. 通过getMenuInflater方法能够得到MenuInflater对象,再调用它的inflate方法就可以给当前活动创建菜单了。
  2. inflate方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单,这里传入R.menu.main.第二个参数用于指定我们的菜单项将添加到哪一个Menu对象中,这里直接使用onCreateOptionsMenu方法传入的menu参数。
  3. 然后给这个方法反悔true,表示允许创建的菜单显示出来,如果返回false,创建的菜单将无法显示。
  4. 重写onOptionsItemSelected定义菜单响应事件。通过调用item.getItemId来判断我们点击的是哪一个菜单项。

2. 隐式Intent

Intent一般可被用于启动活动、启动服务以及发送广播等场景,本节注重讲解隐式Intent。

显式Intent指的是Intent的“意图”非常明显,指明了从哪个活动跳转到哪个活动。隐式Intent并不明确指出启动哪一个活动,而是指定一系列更为抽象的action和category信息,然后交由系统去分析这个intent,并帮我们找到合适的活动去启动。

  • 默认的category的intent

* 首先:创建FirstActivity,并且在配置文件中配置action和category:

public class FirstActivity extends Activity  {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_first);        ButterKnife.inject(this);    }}
<?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/bt_first"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center"        android:text="I am FirstActivity"        android:textSize="50px" /></LinearLayout>
 <activity android:name=".FirstActivity">        <intent-filter>            <action android:name="com.fkq.menu.First" />            <category android:name="android.intent.category.DEFAULT"/>        </intent-filter></activity>

说明:
1. 在标签中我们指明了当前活动可以响应 com.fkq.menu.First 这个action,而标签则包含一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。
2. 只有和中的内容同时能够匹配上Intent指定的action和category时,这个活动才能响应该Intent。

* 其次:修改MainActivity,进行跳转:

    @InjectView(R.id.bt_main)    Button btMain;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.inject(this);        btMain.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                switch (view.getId()){                    case R.id.bt_main:                        startActivity(new Intent("com.fkq.menu.First"));                }            }        });    }

说明:android.intent.category.DEFAULT 是默认的category,在调用startActivity方法的时候会自动将这个category添加到Intent中。

  • 非默认的category的intent

每个Intent中只能指定一个action,但却能指定多个category。

1.修改配置文件的category,即添加一个:

<activity android:name=".FirstActivity">            <intent-filter>                <action android:name="com.fkq.menu.First" />                <category android:name="android.intent.category.DEFAULT"/>                <category android:name="com.fkq.menu.my_category"/>            </intent-filter></activity>

2.修改MainActivity的startActivity部分代码:

Intent intent = new Intent("com.fkq.menu.First");    intent.addCategory("com.fkq.menu.my_category");    startActivity(intent);

3. 更多Intent用法

1. 简单示例

使用隐式Intent,不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。

比如应用程序中需要展示一个网页,只需要调用系统的浏览器来打开这个网页即可。

Intent intent = new Intent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.baidu.com"));startActivity(intent);

说明:
1. 首先指定了Intent的action是Intent.ACTION_ViEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW.
2. 然后通过Uri.parse方法,将一个网址字符串解析承一个Uri对象。
3. 再调用Intent的setData方法将这个Uri对象传递进去。

再次说明:
setData方法接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse方法中解析产生的。

2. 配置data标签

我们可以在标签中再配置一个标签,用于更精确地指定当前活动能够响应什么类型的数据。标签中主要可以配置以下内容:

  1. android:scheme 用于指定数据的协议部分,如上例中的http部分。
  2. android:host 用于指定数据的主机名部分,如上例中的www.baidu.com部分。
  3. android:port 用于指定数据的端口部分,一般紧跟在主机名之后。
  4. android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
  5. android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定。

注意:只有标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。不过一般在标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了。

修改配置文件如下:

 <activity android:name=".FirstActivity">        <intent-filter>            <action android:name="android.intent.action.VIEW" />            <category android:name="android.intent.category.DEFAULT" />            <data android:scheme="http" />        </intent-filter></activity>

说明:标签中通过android:sheme指定了数据的协议必须是http协议,这样就和浏览器一样,能够响应一个打开网页的Intent了。

Intent intent = new Intent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.baidu.com"));startActivity(intent);

除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示拨打电话。

<intent-filter>    <action android:name="android.intent.action.DIAL" />    <category android:name="android.intent.category.DEFAULT" />    <data android:scheme="tel" /></intent-filter>
Intent intent = new Intent(Intent.ACTION_DIAL);intent.setData(Uri.parse("tel:10086"));startActivity(intent);

说明:
1. 首先指定Intent的action是Intent.ACTION_DIAL,这又是Android系统的内置动作。
2. 然后在data部分指定了协议是tel,号码是10086。

4. 向下一个活动传递数据

发送:

String data = "hello world";Intent intent = new Intent(MainActivity.this,FirstActivity.class);intent.putExtra("mydata",data);startActivity(intent);

说明:通过putExtra方法传递一个字符串,接收两个参数,第一个参数是键,用于从Intent中取值,第二个参数才是真正要传递的数据。

接收:

Intent intent = getIntent();String data = intent.getStringExtra("mydata");Toast.makeText(this, "传递过来的值是" + data, Toast.LENGTH_SHORT).show();

说明:通过getIntent方法获取到用于启动FirstActivity的Intent,然后调用getStringExtra方法来获取传递的数据。如果是整型数据,则使用getIntExtra方法,如果是布尔数据,则使用getBooleanExtra方法。

5. 返回数据给上一个活动

Activity中有一个startActivityForResult方法用于启动活动,期望在活动销毁的时候能够返回一个结果给上一个活动。

  • 示例:MainActivity跳转到FirstActivity,FirstActivity销毁返回数据给MainActivity

MainActivity:

@Override    public void onClick(View view) {        switch (view.getId()) {            case R.id.bt_trans:                Intent intent = new Intent(MainActivity.this, FirstActivity.class);                startActivityForResult(intent, 1);        }    }    @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.e("MainActivity",returnedData);                }        }    }

说明:
1. startActivityForResult方法接收两个参数,第一个参数还是intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
2. 使用startActivityForResult方法启动SecondActivity,请求码只要是唯一值就可以了,这里传入了1。

FirstActivity:

Button button = (Button) findViewById(R.id.bt_re_data);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                switch (view.getId()){                    case R.id.bt_re_data:                        Intent intent = new Intent();                        intent.putExtra("data_return","hello");                        setResult(RESULT_OK,intent);                        finish();                        break;                }            }        });

说明:
1. 在FirstActivity里面给按钮添加点击事件,并添加返回数据的逻辑。
2. 构建一个Intent仅仅用于传递数据而已,紧接着把要传递的数据存放在Intent中,然后调用setResult方法,专门用于向上一个活动返回数据的。
3. setResult方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则把带有数据的intent传递回去,然后调用finish方法销毁当前活动。
4. 由于使用startActivityForResult方法来启动FirstActivity的,在FirstActivity被销毁之后会回调MainActivity的onActivityResult方法,因此我们需要在MainActivity中重写这个方法来得到返回的数据。
5. onActivityResult方法带有三个参数,第一个参数requestCode,就是启动活动的时候传入的请求码。第二个参数resultCode,就是我们在返回数据时传入的处理结果。第三个参数data,就是携带着返回数据的Intent。
6. 由于一个活动中可能调用startActivityForResult方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult这个方法中,因此首先要做得就是检查requestCode的值来判断数据来源。确定数据是从FirstActivity返回的之后,再通过resultCode的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。

疑问:
假如FirstActivity中不用按钮来销毁活动,而是通过点击Back键,那如何处理呢?很简单:在FirstActivity中重写onBackPressed方法来解决这个问题。

@Overridepublic void onBackPressed() {    Intent intent = new Intent();    intent.putExtra("data_return","hello");    setResult(RESULT_OK,intent);    finish();}

6. 活动的生命周期

返回栈

Android是用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈。

每当我们启动一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。每当我们按下Back键或调用finish方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。

系统总是会显示处于栈顶的活动给用户。

活动状态

  1. 运行状态:当一个活动处于栈顶时,这个活动就处于运行状态。
  2. 暂停状态:当一个活动不处于栈顶,但是依然可见的时候,这个活动就进入了暂停状态。如:对话框后面的活动。
  3. 停止状态:当一个活动不处于栈顶,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量。当其他地方需要内存时,活动可能会被系统回收。
  4. 销毁状态:当一个活动从返回栈中移除后就变成了销毁状态。
  5. 由于可见的活动被回收,用户体验不好,所以系统最不愿意回收运行和暂停状态的活动,当内存紧张的时候,会优先回收销毁状态和停止状态的活动。

活动的生存期

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

    1. onCreate:
      活动第一次被创建的时候调用,所以在这个活动中完成初始化操作:加载布局,绑定事件等。
    2. onStart:
      活动由不可见变为可见的时候调用。
    3. onResume:
      活动准备好和用户进行交互的时候调用。
    4. onPause:
      这个活动在系统准备去启动或者恢复另一个活动的时候调用。通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但是这个活动执行速度一定要快,不然会影响新的栈顶活动的使用。
    5. onStop:
      这个方法在活动完全不可见的时候调用。与onPause方法主要区别是:如果启动的新活动是一个对话框式的活动,那么onPause方法会得到执行,而onStop方法并不会。
    6. onDestory:
      活动被销毁前调用。
    7. onRestart:
      活动由停止状态变为运行状态之前调用,即重新启动时调用。
  • 以上活动又分为3种生存期

    1. 完整生存期:活动在onCreate方法和onDestroy方法之间所经历的,就是完整生存期。
    2. 可见生存期:活动在onStart和onStop方法之间所经历的,就是可见生存期。在这个生存期内,onStart方法对资源进行加载,而在onStop方法对资源进行释放,合理管理对用户可见的资源。
    3. 前台生存期:活动在onResume方法和onPause方法之间就是前台生存期。

体验活动的生命周期

1.创建MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private static final String TAG = "MainActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Log.d(TAG, "onCreate");        setContentView(R.layout.activity_main);        Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);        Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);        startNormalActivity.setOnClickListener(this);        startDialogActivity.setOnClickListener(this);    }    @Override    public void onClick(View view) {        switch (view.getId()) {            case R.id.start_normal_activity:                startActivity(new Intent(this, NormalActivity.class));                break;            case R.id.start_dialog_activity:                startActivity(new Intent(this, DialogActivity.class));                break;        }    }    @Override    protected void onStart() {        super.onStart();        Log.d(TAG, "onStart");    }    @Override    protected void onResume() {        super.onResume();        Log.d(TAG, "onResume");    }    @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");    }    @Override    protected void onRestart() {        super.onRestart();        Log.d(TAG, "onRestart");    }}

2.创建DialogActivity和NormalActivity:

public class DialogActivity extends AppCompatActivity {    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_dialog);    }}
public class NormalActivity extends AppCompatActivity {    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_normal);    }}

3.修改配置文件:

<activity android:name=".NormalActivity"></activity><activity    android:name=".DialogActivity"    android:theme="@style/Theme.AppCompat.Dialog"></activity>

注意:由于类继承的是AppCompatActivity,所以主题应该是AppCompat里面的,否则会报错。

说明:针对MainActivity
1. 进入应用程序,MainActivity第一次被创建会依次执行onCreate、onStart、onResume方法
2. 启动NormalActivity,MainActivity会执行onPause和onStop方法,因为NormalActivity把MainActivity完全遮掩住了。
3. 按下Back返回MainActivity,MainActivity会执行onRestart方法,然后执行onStart和onResume方法,因为之前MainActivity已经进入了停止状态,所以onRestart方法;之所以onCreate方法没有执行,因为MainActvity并没有重新创建。
4. 启动DialogActivity,MainActivity只会执行onPause方法,onStop方法并没有执行,因为DialogActivity并没有完全遮掩住MainActivity,MainActivity只是进入了暂停状态,并没有进入停止状态。相应地,按下Back键返回MainActivity也应该只有onResume方法会得到执行。
5. 在MainActivty页面按下Back键推出程序,依次执行onPause、onStop、onDestory方法。

7. 活动被回收了怎么办

当活动被销毁以后,可能会销毁掉活动中原有的数据,比如EditText中输入的内容。为了解决这个问题,Android提供了一个onSaveInstanceState回调方法,这个方法可以保证在活动被回收之前一定会被调用。

onSaveInstanceState方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString保存字符串,使用putInt方法保存整形数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。

 @Override    public void onSaveInstanceState(Bundle outState) {        String tempdata = "something you just typed";        outState.putString("data_key",tempdata);        super.onSaveInstanceState(outState);        Log.e(TAG,"此时被调用");    }
@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        if (savedInstanceState!=null){            String tempdata = savedInstanceState.getString("data_key");            Toast.makeText(this,"tempdata::"+tempdata,Toast.LENGTH_SHORT).show();        }    }

说明:

  1. 利用手机的横竖屏切换,可以完美做模拟,先调用onSaveInstanceState,然后调用onCreate。
  2. onCreate方法中有一个Bundle类型的参数。这个参数在一般情况下都是null,但是如果在活动被系统回收之前有通过onSaveInstanceState方法来保存数据的话,这个参数就会带有之前所保存的全部数据。

8. 活动的启动模式

启动模式有4种,分别是standard、singleTop、singleTask和singleInstance,可以在配置文件中通过给标签指定android:launchMode属性来选择启动模式。

standard

不管活动是否已经在返回栈中存在,每次启动都会创建一个新的实例,并且位于栈顶。

singleTop

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

singleTask

每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动示例。

singleInstance

启动一个新的返回栈来管理这个活动。假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的案例,使用之前的三种模式是做不到的,因为每个应用程序都有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这个种模式下会有一个单独的返回栈来管理活动,不管是哪个应用程序来访问这个活动,都共用同一个返回栈,也就解决了共享活动实例的问题。

栗子:FirstActivity跳转到SecondActivity,然后在SecondActivity进入到THirdActivity。SecondActivity单独在一个返回栈,FirstActivity和THirdActivity在一个返回栈。点击Back,ThirdActivity直接返回到FirstActivity,再按下Back键又会返回到SecondActivity,再按下Back键会推出程序。

原理:FirstA和ThridA存放在同一个返回栈,当在ThridA的界面按下Back键,ThridAcitivy会从返回栈出栈,FirstA成为栈顶活动显示在界面上。在FirstA界面上按下Back键,当前的返回栈空了就显示了另一个返回栈的栈顶活动,即SecondActivity,按下Back,所有的返回栈空了,退出程序。

9. 知晓当前是在哪一个活动

创建BaseActivity,让FirstActivity、SecondActivity、ThirdActivity继承BaseAcitify,点击进入FirstActivity、SecondActivity、ThirdActivity,Log就会打印出当前活动的名称。BaseActivity代码如下:

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

10.随时随地退出程序

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

2.新建BaseActivity:

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

3.新建FirstActivity继承BaseActivity:

ActivityCollector.finishAll();android.os.Process.killProcess(android.os.Process.myPid());

说明:
1. FirstActivity继承BaseActivity,启动FirstActivity,FirstActivity这个类就会被保存到集合里面。
2. 如果FirstActivit被系统内存回收销毁后,集合会把FirstActivit移除。
3. 想退出程序,只需清空集合即可。
4. 为了保证程序完全退出,杀掉当前进程:killProcess方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获得当前程序的进程id。

11. 启动活动的最佳写法

假设SecondActivity中需要用到两个非常重要的字符串参数,在启动MainActivity的时候必须要传递过来:

Intent intent = new Intent(this,MainActivity.class);intent.putExtra("param1","data1");intent.putExtra("param2","data2");startActivity(intent);
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);}
MainActivity.actionStart(this,"data1","data2");
0 0