第二章 Activity(看得见的活动)|《第一行代码》笔记


1. 活动是什么

活动是一个包含用户界面的组件,可以理解为一个APP中的页面,是直接展示给用户的组件。

2.活动创建步骤

  1. 项目中new一个Empty Activity,勾选Generate Layout FileActivity自动创建一个布局文件
  2. res/layout下找到与你刚创建的Activity绑定的布局文件,在布局文件中添加对应组件和编辑布局
  3. AndroidManifest.xml中注册声明该活动

主活动配置代码

<intent-filter>
	<action android:name="android.intent.actoin.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

3. 活动的简单使用

3.1 活动中使用Toast的代码

//直接在对应的FirstActivity中编辑下列
protected void onCreate(Bundle savedInstanceState){//在Activity创建阶段就创建绑定好显示型组件
    super.onCreate(savedInstanceState);//重写方法的时候,继承原方法特性
    setContentView(R.layout.first_layout);//绑定布局文件
    Button button1 = (Button) findViewById(R.id.button_1);//新建和绑定组件
    button1.setOnClickListener(new View.OnClickListener(){//为Button组件设置监听事件
        @Override
        public void onClick(View v){//设置监听到button1被按下时候执行下列事件
            //通过静态方法makeText创建出一个Toast对象,其三个参数分别为Context,Text,Time;再链式调用show()方法显示
            Toast.makeText(FirstActivity.this,"You click Button",
                          Toast.LENGTH_SHORT).show();
        }
    });
}

3.2 活动中使用Menu的代码

到资源文件夹中新建menu文件夹,在该文件夹中新建menu资源文件

<!--menu资源文件,此处为简单试用,只需添加id和title属性-->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
    <item
        android:id="@+id/remove_item"  
        android:title="Remove"/>
</menu>

回到FirstActivity中重写关于menu的对应方法

//在OnCreate方法之外编写以下的方法,inflater(充气机)
@Override
   public boolean onCreateOptionsMenu(Menu menu) {//重写该方法载入绑定menu资源文件
       getMenuInflater().inflate(R.menu.main,menu);//调用该方法绑定
       return true;//true为显示menu,false为不显示
   }

   @Override
   public boolean onOptionsItemSelected(@NonNull MenuItem item) {//重写该方法,定义菜单响应事件
       switch (item.getItemId()){
           case R.id.add_item:
               Toast.makeText(this,"You clicked add_item",Toast.LENGTH_SHORT).show();
               break;
           case R.id.remove_item:
               Toast.makeText(this,"You clicked remove_item",Toast.LENGTH_SHORT).show();
               break;
           default:
               break;
       }
       return true;//记得返true
   }

4. 使用Intent在活动中穿梭

4.1 显式Intent

//新建SecondActivity之后,使用Intent从FirstActivity跳转到SecondActivity中
//修改FirstActivity为
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //new一个Intent重载
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);//调用startActivity()重载
            }
        });
    }

4.2 隐式Intent

概述:恰如其意,隐式调用不直接指明某一个类,而是指定actioncategory(类别)等信息,让系统去分析应该跳转到哪个app的哪个活动之中。

注意:

  1. 目标Activity需要在AndroidManifest.xml文件中配置好<intent-filter>的内容,指定好能响应的actioncategory等信息。而在执行IntentActivity中,也需要同时匹配上这对应的actioncategory,活动才可以响应。
  2. 每个Intent中只能指定一个action,但是却可以同时指定多个categorycategory是包含了附加信息的标签,其功能是让Intent精准定位到对应的Activity中。由于action的编写是相对统一的,例如手机中的浏览器都具有被调用打开网页的功能,而它们配置的action都是“Intent.ACTION_VIEW”;多个category的作用在于,当我想固定调用QQ浏览器时,在Intent跳转的时候载入QQ浏览器中配置的category标签即可无需选择直接打开QQ浏览器。
  3. 配置<data>标签,详细看下例
  • 普通调用

    <!--AndroidManifest.xml文件中SecondActivity的配置-->
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    //Activiy中的Intent跳转
     button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //此处无需指定category,因为它是默认的,调用startActivity的时候会自动添加进去
                    Intent intent = new Intent("com.example.activitytest.ACTION_START");
                    startActivity(intent);
                }
            });
  • 多category调用

    <!--AndroidManifest.xml文件中SecondActivity的配置-->
    <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>
    //Activiy中的Intent跳转
     button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("com.example.activitytest.ACTION_START");
                    //此处配置文件中有多个category,需要指定非默认的category
                    intent.addCategory("com.example.activitytest.MY_CATEGORY");
                    startActivity(intent);
                }
            });
  • <data>标签的用法

    <data>标签用于更精确地指定当前活动能够响应什么类型的数据,主要可以配置以下活动

    tips:下面涉及到计算机网络的知识,入门可以参考《图解HTTP》(提取码:6gpm)

    android:scheme 用于指定数据的协议部分,如下边的http部分

    android:host 指定数据的主机名部分,如下边的www.baidu.com部分

    android:port 用于指定数据的端口部分,一般紧随在主机名之后

    android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容

    android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定

    button1.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   Intent intent = new Intent(Intent.ACTION_VIEW);
                   //此处键入对应的的data,Uri.parse方法把字符串解析为uri对象
                   intent.setData(Uri.parse("http://www.baidu.com"));
                   
                   //跳转到系统拨号界面
                   //Intent intent = new Intent(Intent.ACTION_DIAL);
                   //intent.setData(Uri.parse("tel:10086"));
                   startActivity(intent);
               }
           });
    <!--AndroidManifest.xml文件中ThirdActivity的配置,指定了数据协议必须是http-->
    <activity android:name=".ThirdActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
    
            <category android:name="android.intent.category.DEFAULT" />
    
            <data android:scheme="http" />
            <!--其他协议:-->
    		<!--geo表示显示地理位置-->
    		<!--tel表示拨打电话-->
        </intent-filter>
    </activity>

    上例中的http协议在Android7中适用,但是在Android9中开始不适用.新版本默认支持https点击查看区别

    我们需要在AndroidManifest.xml中如下配置,以支持HTTP明文

    法1:在res目录下新建xml目录,在该目录下新建配置文件 network_security_config.xml,内容为:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true" />
    </network-security-config>

    然后在AndroidManifest.xml文件的applicaiton段增加了networkSecurityConfig一行

    <application
                 ...
     android:networkSecurityConfig="@xml/network_security_config"
                 >
    ...
    </application>

    法2:直接在AndroidManifest.xml文件的applicaiton段增加了usesCleartextTraffic一行

    <application
                 ...
     android:usesCleartextTraffic="true"
                 >
    ...
    </application>

4.3 利用Intent向下一个活动传数据

//FirstActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.first_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String data = "Hello Word";
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
            intent.putExtra("extra_data",data);//第一个参数是键,第二个参数是真正的value(数据)
            startActivity(intent);
            
            //调用封装的方法传递数据
            //SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
        }
    });
}
//SecondActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second_layout);
    Intent intent = getIntent();
    String data = intent.getStringExtra("extra_data");
	//对应的还有:getIntExtra(),getBooleanExtra()等方法
}

//将传递数据的功能封装,方便之后的调用
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);
    }

4.4 利用Intent向上一个活动返回数据

startActivityForResult(Intent intent,int requestCode ) 该方法也是用于启动活动的,但这个方法期望在下一个活动被销毁的时候能够返回一个结果给本活动。顾名思义,该函数声明本活动允许接受下一个活动返回的结果

//FirstActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
	button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
            //第二个参数的请求码只需要唯一即可,在重写OnActivityResult()方法的时候会使用到。建议取大于1的值
         	startActivityForResult(intent,1);
        }
    });
}

//在SecondActivity销毁的时候会回调这一个方法
/**
* @param requestCode 请求码,对应为startActivityForResult中的1
* @param resultCode 返回数据的结果,对应为setResult()的第一个参数
* @param data 数据,即对应的Intent
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {//startActivityForResult(Intent intent,int requestCode )传入的1
        case 1:
            if (resultCode == RESULT_OK) {
                String returnedData = data.getStringExtra("data_return");
                Log.d("FirstActivity", returnedData);
            }
            break;
        default:
    }
}
//SecondActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("SecondActivity", "Task id is " + getTaskId());
    setContentView(R.layout.second_layout);
    Button button2 = (Button) findViewById(R.id.button_2);
    button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {//在点下button后向上一活动发送返回值,并且关闭该活动
            Intent intent = new Intent();
            intent.putExtra("data_return", "Hello FirstActivity");
            //本方法专门用于返回处理结果,第一个参数可选RESULT_OK或RESULT_CANCELED两个值,第二个则为带数据的intent
    		setResult(RESULT_OK, intent);
    		finish();
        }
    });
}

@Override
public void onBackPressed() {//实现在直接按返回键的时候也达到一样的功能
    Intent intent = new Intent();
    intent.putExtra("data_return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);
    finish();
}

温馨提示:最新的sdk中,startActivityForResult方法已经被弃用,想了解和掌握新的API使用方法,参照下面的两篇博客

Activity Result API详解,是时候放弃startActivityForResult了_郭霖的专栏-CSDN博客

startActivityForResult被标记为弃用后,如何优雅的启动Activity?_葫芦娃-CSDN博客

5. 活动的生命周期

5.1 活动状态

官方指南

  1. 运行状态:活动处于栈顶的时候,系统最不愿回收
  2. 暂停状态:不在栈顶,但仍可见(部分可见)的活动;即跳出一个小弹窗后,下方被覆盖的活动即属于暂停状态。系统也不愿回收
  3. 停止状态:不在栈顶,不可见的活动;即被新的Activity完全覆盖的活动,系统可能回收
  4. 销毁状态:不在栈中的活动;即被finish()的活动,系统最愿意回收

5.2 活动的生存期

官方指南

  1. 完整生存期
  2. 可见生存期

  1. onCreate() 活动第一次创建的时候会调用这个方法,我们需要在这个方法中完成活动的初始化操作,例如加载布局文件和绑定事件。接下来就进入到onStart()
  2. onStart() 活动由不可见变可见,Activity进入前台,但是仍无法交互。当 Activity 进入已开始状态时,与 Activity 生命周期相关联的所有生命周期感知型组件都将收到 ON_START事件。,这个状态会迅速完成,进入到onResume()
  3. onResume() 活动进入到可交互阶段,即正常使用的状态。此时活动一定位于栈顶,正在运行阶段
  4. onPause() 此方法表示Activity不再处于前台(多窗口模式时也是),但仍然可见。这个方法是暂停,在准备去启动或者恢复另一个活动的时候调用。譬如弹出弹窗,或者在多窗口模式下点击另一活动的时候;本活动便调用onPause()方法,且不处于焦点之中。这时候,系统会释放掉一些与界面无关的消耗CPU的资源。
  5. onStop() 该方法在活动完全不可见的时候调用,此时活动被完全覆盖,进入了停止状态。对应的与界面有关的效果会停止更新或者粗略更新。我们能重写该方法,在活动调用该方法的时候保存应用或者用户数据、进行网络调用或者执行数据库事务
  6. onDestroy() 在活动被销毁前调用该方法,包括①Activity即将结束(被彻底关闭或执行了finish()); ②配置变更(设备旋转或执行了多窗口模式),Activity被暂时销毁,之后还会重新启用onCreate()。我们可以在该回调释放前面回调未释放的资源。当然,我们也可以配置使得当配置变更时不会执行此回调,这样子在启动全屏播放视频时候不会出现差的用户体验
  7. onRestart()onStop()变为onStart()前调用。即被覆盖的页面重新被打开的时候,需要被调用。

5.3 活动被回收怎么办

前言:当活动被回收之后,再次打开会使用到onCreate()而不是使用onStart(),这种情况下的活动是被重新创建的。当活动被重新创建,我们在页面中保存的数据(譬如输入文本框中的文字)会被清除掉,这样会严重影响用户的使用体验。

解决方法(类似Intent):

  1. 使用onSaveInstanceState()回调方法,此方法保证活动被回收前一定会被调用。重写此方法能将被回收活动的数据保存下来,以便重新创建活动的时候能够复现出来。

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        String tempData = "Something you just typed";
        //该参数下的链式方法包括 putString() putInt() 等
        //对应方法的参数是键值对形式,第一个参数是键,第二个参数是值
        outState.putString("data_key",tempData);
    }
  2. 对应的,我们也需要在onCreate()中处理Bundle类型的数据,让其在重新创建的时候复现出来

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

Tips:上例是Bundle数据的存储和取出方式。我们可以把Bundle数据存储到Intent中,然后借助Intent来进行数据传输

6.活动的启动模式

我们可以通过在AndroidManifest.xml中给标签指定android:launchMode的属性,即可确定活动的启动方式

6.1 standard

不会在乎活动是否在返回栈中存在,每次启动都会创建一个新的实例(系统默认的)

6.2 singleTop

启动的时候如果发现返回栈的栈顶是该活动,则不会启动新的,直接使用。否则,启动新的活动

6.3 singleTask

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

6.4 singleInstance

假设我们的程序中有一个活动是允许其他程序调用的(例如从一个APP的广告页面点击到达购物软件的商品详情窗口),我们需要实现其他程序和我们的程序可以共享这个活动的实例,就要使用到singleInstance。这个共享的实例有自己单独的一个返回栈来管理活动(譬如返回栈B)。

7. 活动的最佳实践

7.1 知晓当前活动

BaseActivity,在该类中加入Log.d("BaseActivity", getClass().getSimpleName());;让所有的Activity继承BaseActivity,这样子就能每个Activity都打印出来了

7.2 设置能随时退出程序

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

}
//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);//调用出栈
    }

}
//ThirdActivity
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();//调用清栈
                android.os.Process.killProcess(android.os.Process.myPid());//杀掉当前线程
            }
        });

    }
}

7.3 启动活动最佳写法

优点:在实际合作开发对接中,让对接者直观了解启动本活动需要传递哪些数据

//SecondActivity
//将传递数据的功能封装,方便之后的调用。让调用者直观了解需要传入什么数据
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);
    }
//FirstActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.first_layout);
    Button button1 = (Button) findViewById(R.id.button_1);
    button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //调用封装的方法传递数据
            SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
        }
    });
}

文章作者: DYJ
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 DYJ !
评论
  目录