第一行代码 Ch.2

文章目录

  • Ch.II :
    • 2.2 活动的基本用法:
      • 新建布局并添加控件:
      • 在活动中使用Toast:
      • 在活动中使用Menu:
      • 销毁一个活动:
    • 2.3使用Intent 在活动之间穿梭:
      • 使用显示Intent:
      • 使用隐式Intent:
      • Intent介绍:
      • 更多intent用法:
      • 向下一个活动传递数据:
      • 返回上一个活动的数据:
    • 2.4 活动的生命周期:
      • back stack:
      • 活动状态:
      • 活动的生存周期:
      • onSaveInstanceState()方法:
      • 实际测试:
    • 2.5 活动的启动模式:
      • standard
      • singleTop
      • singleTask:
      • singleInstance
    • 2.6 活动的最佳实践:
      • 知晓当前在哪一个活动:
      • 随时随地的退出程序:
      • 启动活动的最佳写法:
      • 启动活动的最佳写法:

Ch.II :

2.2 活动的基本用法:

新建布局并添加控件:

在相应的位置(Ch.1中介绍过) 创建.java的Activity文件和.xml的布局文件, 创建后, AS会自动将Activity在manifests中注册:

    <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">activity>			//此为新增Activity
    application>

可以使用AS中的可视化编辑, 这里介绍直接在xml中添加控件:

添加一个Button元素后的空间布局xml:


<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:id="@+id/button_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button_1"
        />
LinearLayout>

其中

  • xml 语法 android:id="@+id/button_1" , 类似于上头的引用
    当写成这样时就是引用: android:id="@+id/button_1", 多一个+时就是创建

  • android:layout width指定了当前元素的宽度,这里使用match parent 表示让当前元素和父元素一样宽

  • android:layout height 指定了当前元素的高度,这里使用wrap_content表示当前元素的高度只要能刚好包含里面的内容就行

  • android:text指定了元素中显示的文字内容

    这里如果没有在res中的string中定义内容再引用, 而是直接定义, AS会给出警告, 所以推荐这么做

而后在MainActivity中加载这个布局:

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
    }
}

使用资源的操作在Ch.1中介绍过

而后, 设置程序启动后的入口, 在Ch.1中也介绍过:

在活动中使用Toast:

Toast是一个弹出后会很快消失的小提示框, 可以很方便的通知信息并且不会占用屏幕的布局空间

在代码中使用Toast:

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        //添加的代码:
        Button Button_1 = findViewById(R.id.button_1);
        Button_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //内部类语法: FirstActivity.this表示外部类的引用
                Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
  • 通过findViewById()方法获取到在布局文件中定义的元素,并返回view

    这里传入R.id.button 1, 即Button_1 的ID,来得到按钮的实例,而后强制转换为Button对象

  • 调用setonClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。因此需要咋onClick() 中编写相关代码

  • 通过静态工厂方法makeText()创建出一个Toast对象,然后调用show()将Toast显示

    这里需要注意的是,makeText()方法需要传入3个参数

    • 第一个参数是Context,也就是Toast要求的上下文,由于Activity本身就是一个Context对象,因此这里直接传入FirstActivity.this即可
    • 第二个参数是Toast显示的文本内容
    • 第三个参数是Toast显示的时长
      有两个内置常量可以选择Toast.LENGTH_SHORT和Toast.LENGTH_LONG。

在活动中使用Menu:

添加menu菜单:

	@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }
  • 这里使用onCreateOptionMenu()方法

    其实Activity中有一套机制实现对菜单的管理

    使用onCreateOptionsMenu & onPrepareOptionsMenu 来添加菜单
    使用onOptionsItemSelected来监听菜单中的Item, 并设置点击后执行的代码

    @Override
      public boolean onCreateOptionsMenu(Menu menu) {
        /**
         * 此方法用于初始化菜单,其中menu参数就是即将要显示的Menu实例。 返回true则显示该menu,false 则不显示;
         * (只会在第一次初始化菜单时调用) Inflate the menu; this adds items to the action bar
         * if it is present.
         */
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
      }
     
      @Override
      public boolean onPrepareOptionsMenu(Menu menu) {
        /**
         * 在onCreateOptionsMenu执行后,菜单被显示前调用;如果菜单已经被创建,则在菜单显示前被调用。 同样的,
         * 返回true则显示该menu,false 则不显示; (可以通过此方法动态的改变菜单的状态,比如加载不同的菜单等) 
         */
        return super.onPrepareOptionsMenu(menu);
      }
     
      @Override
      public void onOptionsMenuClosed(Menu menu) {
        /**
         * 每次菜单被关闭时调用. (菜单被关闭有三种情形,menu按钮被再次点击、back按钮被点击或者用户选择了某一个菜单项)
         */
        super.onOptionsMenuClosed(menu);
      }
     
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
        /**
         * 菜单项被点击时调用,也就是菜单项的监听方法。 
         * 通过这几个方法,可以得知,对于Activity,同一时间只能显示和监听一个Menu 对象。 
         */
        return super.onOptionsItemSelected(item);
      }
    
    
  • 而后, 内部使用getMenuInflater().inflate(R.menu.main,menu);将布局文件中的菜单添加进去

    本方法主要的好处是实现了模型(Model)与视图(View)的分离

    需要在res目录中添加menu文件夹, 并在其中创建Menu resource file 才能使用

    其中:

    • 调用Activity的getMenuInflater()得到一个MenuInflater

      MenuInflater是用来加载menu布局文件的, 与LayoutInflater类似,应用程序运行时会预先加载资源中的布局文件,如果Menu布局中的资源比较多,会影响性能,所以可以选择MenuInflater方式用的时候加载,这样减轻了应用程序运行时很多负担

    • 使用inflate方法来把布局文件中的定义的菜单(第一个参数)加载给指定的menu对象(第二个参数)

    • MenuInflater的标准使用方法:

      只有Activity.getMenuInflater()方法和MenuInflater(Context context)方法来获得MenuInflater对象, 然后调用MenuInflater的方法inflate(int menuRes, Menu menu)方法加载布局


    这里介绍另一种加载菜单的方法:

    public boolean onCreateOptionsMenu(Menu menu) { 
            super.onCreateOptionsMenu(menu); 
            menu.add(Menu.NONE,  Menu.First+1 , 0, "设置").setIcon(R.drawable.setting); 
            return true; 
        } 
    

    使用add()方法, 但是由于没有将模型(Model)与视图(View)的分离, 所以现在基本不用…

销毁一个活动:

  1. 使用back键销毁当前显示的活动

  2. 在程序中, 使用Activity的finish() 方法销毁当前活动

    想测试一下显示Toast再延时3000ms退出, 但是发现Toast并没有被显示, 即使再开一条线程也是一样…后头在看啥原因

    		Button Button_2= (Button) findViewById(R.id.button_2);
            Button_2.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View v) {
                    Thread toast= new Thread(new Runnable(){
                        @Override
                        public void run() {
                            Toast.makeText(FirstActivity.this,"Activity deleting",Toast.LENGTH_SHORT).show();
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    finish();	//这里调用finish(), 结束finish所在的Activity
                }
            });
    

2.3使用Intent 在活动之间穿梭:

Ch.1中介绍的intent:

Intent 并不是 Android 应用程序四大核心组件之一,但是其重要性无可替代

Android 应用程序核心组件中的三大核心组件 —— Activity、Service、BroadcastReceiver。通过消息机制被启动激活,而所使用的消息就是 Intent。Intent 是对即将要进行的操作的抽象描述,承担了 Android 应用程序三大核心组件相互之间的通信功能

现在由于只介绍到了Activity, 所以暂时只涉及Intent与Activity之间的联系

使用显示Intent:

显式,即直接指定需要打开的activity对应的类

  1. 构造方法:

    Intent intent = new Intent(this, SecondActivity.class);  
    startActivity(intent);  
    

    直接在构造函数中传入指定打开的Activity类, 这是最常用的方法

    其中intent的构造函数:

    	public Intent(Context packageContext, Class<?> cls) {
            mComponent = new ComponentName(packageContext, cls);
        }
    

    其中第二个参数是Class类:

    Class类保存的是类的类型信息, 相当于对象的抽象类型集合

    有三种获得Class对象的方式:

    1. Class.forName(“类的全限定名”)
    2. 实例对象.getClass()
    3. 类名.class (类字面常量)
  2. 拐弯抹角setComponent()方法:

    Intent intent = new Intent();    
    intent.setClass(this, SecondActivity.class);  
    //或者intent.setClassName(this, "com.example.app.SecondActivity");  
    //或者intent.setClassName(this.getPackageName(),"com.example.app.SecondActivity");            
    startActivity(intent);  
    
  3. 类似的setClass / setClassName方法

    Intent intent = new Intent();    
    intent.setClass(this, SecondActivity.class);  
    //或者intent.setClassName(this, "com.example.app.SecondActivity");  
    //或者intent.setClassName(this.getPackageName(),"com.example.app.SecondActivity");            
    startActivity(intent);  
    

其中, Activity类提供了两种启动Activity的方法:

Context.startActvity();
Activity.startActivityForResult();

使用隐式Intent:

隐式,不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity。筛选是根据所有的来筛选

intent匹配模式:

第一行代码 Ch.2_第1张图片

实现:

首先在项目的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>

为SecondActivity添加了intent_filter内容

其中action&category可以自定义, 但是通常设置为包名+action名的形式, 便于与其他包进行区分
android.intent.category.DEFAULT是默认的一种category, 会在startAction()调用时自动添加进intent中

而后在程序中设置:

Button_1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
        Intent intent= new Intent("com.example.activitytest.ACTION_START");
        startActivity(intent);
    }
});

如果使用是其他category ,需要使用addCategory()方法添加到intent对象中
其中每个intent对象可以添加多个category

Intent介绍:

Intent对象大致包括7大属性:Action(动作)Data(数据)Category(类别)Type(数据类型)Component(组件)Extra(扩展信息)Flag(标志位)。其中最常用的是Action属性和Data属性。

  • **Action:**用来表现意图的行动
    一个字符串变量,可以用来指定Intent要执行的动作类别。常见的action有:
    Activity Actions:
类型 作用
ACTION_MAIN 表示程序入口
ACTION_VIEW 自动以最合适的方式显示Data
ACTION_EDIT 提供可以编辑的
ACTION_PICK 选择一个一条Data,并且返回它
ACTION_DAIL 显示Data指向的号码在拨号界面Dailer上
ACTION_CALL 拨打Data指向的号码
ACTION_SEND 发送Data到指定的地方
ACTION_SENDTO 发送多组Data到指定的地方
ACTION_RUN 运行Data,不管Data是什么
ACTION_SEARCH 执行搜索
ACTION_WEB_SEARCH 执行网上搜索
ACRION_SYNC 执同步一个Data
ACTION_INSERT 添加一个空的项到容器中

Broadcast Actions:

类型 作用
ACTION_TIME_TICK 当前时间改变,并即时发送时间,只能通过系统发送。调用格式"android.intent.action.TIME_TICK"
ACTION_TIME_CHENGED 设置时间。调用格式"android.intent.action.TIME_SET"
  • **Data:**表示与动作要操纵的数据
    一个URI对象是一个引用的data的表现形式,或是data的MIME类型;data的类型由Intent的action决定

    类型 作用
    android:scheme 用于指定数据的协议部分,如上例中的http部分。
    android:host 用于指定数据的主机名部分,如上例中的www.baidu.com部分。
    android:port 用于指定数据的端口部分,一般紧随在主机名之后。
    android:path 用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
    android:mimeType 用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
  • **Category:**用来表现动作的类别
    一个包含Intent额外信息的字符串,表示哪种类型的组件来处理这个Intent。任何数量的Category 描述都可以添加到Intent中,但是很多intent不需要category,下面列举一些常用的category:

    类型 作用
    CATEGORY_DEFAULT 把一个组件Component设为可被implicit启动的
    CATEGORY_LAUNCHER 把一个action设置为在顶级执行。并且包含这个属性的Activity所定义的icon将取代application中定义的icon
    CATEGORY_BROWSABLE 当Intent指向网络相关时,必须要添加这个类别
    CATEGORY_HOME 使Intent指向Home界面
    CATEGORY_PREFERENCE 定义的Activity是一个偏好面板Preference Panel
  • **Type:**指定数据类型
    一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。

  • **Component:**目的组件
    指定Intent的目标组件名称,当指定了这个属性后,系统将跳过匹配其他属性,而直接匹配这个属性来启动对应的组件。

  • **Extra:**扩展信息
    Intent可以携带的额外 key-value 数据,你可以通过调用putExtra()方法设置数据,每一个 key对应一个 value数据。你也可以通过创建 Bundle对象来存储所有数据,然后通过调用putExtras()方法来设置数据。

类型 作用
EXTRA_BCC 存放邮件密送人地址的字符串数组
EXTRA_CC 存放邮件抄送人地址的字符串数组
EXTRA_EMAIL 存放邮件地址的字符串数组
EXTRA_SUBJECT 存放邮件主题字符串
EXTRA_TEXT 存放邮件内容
EXTRA_KEY_EVENT 以KeyEvent对象方式存放触发Intent 的按键
EXTRA_PHONE_ NUMBER 存放调用ACTION_CALL 时的电话号码
  • **Flag:**期望这个意图的运行模式
    用来指示系统如何启动一个Activity,可以通过setFlags()或者addFlags()可以把标签flag用在Intent中。
类型 作用
FLAG_ACTIVITY_CLEAR_TOP 相当于SingleTask
FLAGE_ACTIVITY_SINGLE_TOP 相当于SingleTop
FLAG_ACTIVITY_NEW_TASK 类似于SingleInstance
FLAG_ACTIVITY_NO_HISTORY 当离开该Activity后,该Activity将被从任务栈中移除

更多intent用法:

  • 概览一下intent构造函数的参数

第一行代码 Ch.2_第2张图片

  • 简要介绍uri:

    通用资源标志符(Universal Resource Identifier, 简称"URI")。

    Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示

    Uri一般由三部分组成:

    • 访问资源的命名机制

    • 存放资源的主机名

    • 资源自身的名称,由路径表示

    通常使用Uri 的parse() 方法将资源转化为Uri:

    	public static Uri parse(String uriString) {
            return new StringUri(uriString);
        }
    
  • 简要介绍setData:

    	public @NonNull Intent setData(@Nullable Uri data) {
            mData = data;
            mType = null;
            return this;
        }
    

    接收一个Uri对象, 并转化为为目标intent对象设置Data

  • 调用拨号程序

// 调用拨打电话,给10010拨打电话
Uri uri = Uri.parse("tel:10010");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);
// 直接拨打电话,需要加上权限  
Uri callUri = Uri.parse("tel:10010"); 
Intent intent = new Intent(Intent.ACTION_CALL, callUri); 
  • 发送短信或彩信
 // 给10010发送内容为“Hello”的短信
Uri uri = Uri.parse("smsto:10010");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");
startActivity(intent);
// 发送彩信(相当于发送带附件的短信)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("sms_body", "Hello");
Uri uri = Uri.parse("content://media/external/images/media/23");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png");
startActivity(intent);
  • 通过浏览器打开网页
// 打开百度主页
Uri uri = Uri.parse("https://www.baidu.com");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
  • 发送电子邮件
// 给[email protected]发邮件
Uri uri = Uri.parse("mailto:[email protected]");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
// 给[email protected]发邮件发送内容为“Hello”的邮件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "[email protected]");
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("text/plain");
startActivity(intent);
// 给多人发邮件
Intent intent=new Intent(Intent.ACTION_SEND);
String[] tos = {"[email protected]", "[email protected]"}; // 收件人
String[] ccs = {"[email protected]", "[email protected]"}; // 抄送
String[] bccs = {"[email protected]", "[email protected]"}; // 密送
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_BCC, bccs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("message/rfc822");
startActivity(intent);
  • 显示地图与路径规划
 // 打开Google地图中国北京位置(北纬39.9,东经116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
// 路径规划:从北京某地(北纬39.9,东经116.3)到上海某地(北纬31.2,东经121.4)
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=39.9 116.3&daddr=31.2 121.4");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
  • 播放多媒体
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/foo.mp3");
intent.setDataAndType(uri, "audio/mp3");
startActivity(intent);

Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
  • 选择图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 2);
  • 拍照
 // 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 1);
 // 取出照片数据
Bundle extras = intent.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
  • 获取并剪切图片
// 获取并剪切图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 开启剪切
intent.putExtra("aspectX", 1); // 剪切的宽高比为1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 保存图片的宽和高
intent.putExtra("outputY", 40);
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);
// 剪切特定图片
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setClassName("com.android.camera", "com.android.camera.CropImage");
intent.setData(Uri.fromFile(new File("/mnt/sdcard/temp")));
intent.putExtra("outputX", 1); // 剪切的宽高比为1:2
intent.putExtra("outputY", 2);
intent.putExtra("aspectX", 20); // 保存图片的宽和高
intent.putExtra("aspectY", 40);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true);
intent.putExtra("output", Uri.parse("file:///mnt/sdcard/temp"));
startActivityForResult(intent, 0);
  • 打开手机应用市场
// 打开手机应用市场,直接进入该程序的详细页面
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
  • 安装程序
String fileName = Environment.getExternalStorageDirectory() + "/myApp.apk";   
Intent intent = new Intent(Intent.ACTION_VIEW);   
intent.setDataAndType(Uri.fromFile(new File(fileName)),
"application/vnd.android.package-archive");   
startActivity(intent);  
  • 卸载程序
Uri uri = Uri.parse("package:" + packageName);   
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
  • 进入设置界面
// 进入系统设置界面
Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
startActivity(intent);

向下一个活动传递数据:

使用Intent类的putExtra()方法 , 将数据暂存在intent中, 启动之后, 在从中取出即可

Intent中提供了一大堆的重载方法, 可以满足基本需求
并且其中都以键值对的形式储存

第一行代码 Ch.2_第3张图片

在启动的程序中调用intent并取出数据:

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);
    }
}
  • getIntent()方法获取到用于启动SecondActivity的Intent

  • getStringExtra()方法,传入相应的键值,可以得到传递的数据

    Intent中针对不同的类型, 有不同的get方法

返回上一个活动的数据:

即Activity_1启动了Activity_2, 当Activity_2结束后, 将数据传回Activity_1

实现这个功能, 首先需要在Activity_1 中使用startActivityForResult()方法启动Activity_2, 而不是使用之前的startActivity()方法:

第一行代码 Ch.2_第4张图片

  • 第一个参数intent: 作用于startActivity() 相同
  • 第二个参数requestCode: 作为后头判断的标志码之一, 下头很快会用到

这里修改按钮点击事件:

从first_activity启动second_activity:

Button Button_1 = (Button) findViewById(R.id.button_1);
        Button_1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(FirstActivity.this, "You Checked Button_1", Toast.LENGTH_SHORT).show();
                Intent intent =new Intent("com.example.activitytest.ACTION_START");
                intent.putExtra("data_return", "First call Second.");

                startActivityForResult(intent, 1);
            }
        });

在second_activity中修改如下:

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

        Button button_2 = findViewById(R.id.button_2);
        button_2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent =new Intent();
                intent.putExtra("return_data", "Hello FirstActivity");
                setResult(RESULT_OK, intent);
                finish();
            }
        });
    }

点击按钮后会结束second_activity, 并向上一个活动first_activity传递数据, 其中数据用一个intent对象保存

setResult()函数设置返回的数据:

第一行代码 Ch.2_第5张图片

  • 第一个参数resultCode标志码, 可用于后头的判断

    通常只使用RESULT_CANCELED 或 RESULT OK这两个值

  • 第二个参数是返回的数据 ,用Intent对象储存, 其中的intent就是用来传递数据的, 没其他用途

而后, 当second_activity被销毁后, 会调用上一个活动的onActivityResult()方法, 所以这里需要对first_activity中的方法进行重载:

第一行代码 Ch.2_第6张图片

  • 头两个参数就是之前设置的标志码

    通常在onActivityResult()方法中使用这两个标志码判断是什么对象返回的数据, 返回啥数据, 而后执行相应的代码

  • 第二次参数就是返回的数据

    可在onActivityResult()方法中提取

    注意这里getExtra获取信息时输入的键值要相同, 否则返回nullPtr

	@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case '1': {
                if (resultCode == RESULT_OK) {
                    String returnData = data.getStringExtra("return_data");
                    Log.d("FirstActivity", returnData);
                }
                break;
            }
            default:
        }
        return;
    }

而后可在AS的Logcat中收到D/FirstActivity: Hello FirstActivity

2.4 活动的生命周期:

back stack:

第一行代码 Ch.2_第7张图片

活动状态:

  1. 运行状态

    当一个活动位于返回栈的栈顶时,此时活动就处于运行状态。系统不会回收处于运行状态的活动

  2. 暂停状态

    当一个活动不再处于栈顶位置,但仍然可见时,这个活动就进入了暂停状态。因为并不是每个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域。处于暂停状态的活动仍然是完全存活着的,系统一般不会回收这种活动,只有在内存极低的情况下,系统才会主动考虑回收这种活动。

  3. 停止状态

    当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

  4. 销毁状态

    当一个活动从返回栈中移除后就变成了销毁状态。系统就会回收这种状态的活动,从而保证内存充足。

活动的生存周期:

第一行代码 Ch.2_第8张图片

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

  • onCreate();

    这个方法在活动第一次被创建的时候调用,在此方法中完成活动的初始化操作,比如加载布局、绑定事件等。

  • onStart();

    这个方法在活动由不可见变为可见的时候调用。

  • onResume();

    这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

  • onPause();

    这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用

  • onStop();

    这个方法在活动完全不可见的时候调用。它和 onPause() 方法的主要区别:如果启动的新活动是一个对话框式的活动,那么 onPause() 方法会得到执行,而 onStop() 方法并不会执行

    就是上头暂停状态和停止状态的区别

  • onDestroy();

    这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

  • onRestart();

    这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上 7 个方法中除了 onRestart() 方法,其他都是两两相对的,从而又可以将活动分为 3 中生存周期

完整生存期

活动在 onCreate() 方法和 onDestroy() 方法之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate() 方法中完成各种初始化操作,而在 onDestroy() 方法中完成释放内存的操作。

可见生存期

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

前台生存期

活动在 onResume() 方法和 onPause() 方法之间所经历的就是前台生存期。在前台生存期内,活动总是处于运行状态,此时的活动是可以和用户进行交互的,平时看到和接触最多的就是前台生存期下的活动。

onSaveInstanceState()方法:

当activity进入stop状态后, 有可能被系统finish释放内存并销毁, 而在这之前必定还会调用一个onSaveInstanceState()方法
如果销毁后又需要start这个activity ,则会中onCreate()方法开始

onSaveInstanceState()方法主要用于在activity将要被销毁时, 保存其中的数据, 并在调用onCreate()重新创建activity时将数据恢复

第一行代码 Ch.2_第9张图片

第一行代码 Ch.2_第10张图片

可以看到, onSaveInstanceState()方法和onCreate()方法都有一个Bundle类型的参数, 这俩就是数据保存于读取的关键:

  • 通常情况下, onCreate() 的Bundle对象是null, 但是在onSaveInstanceState()方法中, 将数据保存到Bundle对象中 ,而后在调用onCreate()时, 又会将这个Bundle对象传入

Bundle简介:

Bundle主要用于传递数据;它保存的数据,是以key-value(键值对)的形式存在的。

  • Bundle经常使用在Activity之间或者线程间传递数据,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。
  • 当Bundle传递的是对象或对象数组时,必须实现Serializable或Parcelable接口。
  • Bundle提供了各种常用类型的putXxx()/getXxx()方法,用于读写基本类型的数据。(各种方法可以查看API)

Bundle和Intent区别

  • Bundle只是一个信息的载体,内部其实就是维护了一个Map
  • Intent负责Activity之间的交互,内部是持有一个Bundle的。

实际测试:

仅记录有用内容

  1. 使用系统内置的Theme:

    Activity注册的.xml文件:

    
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.activitylifecycletest">
    
        <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=".DialogActivity"
                android:theme="@style/Theme.AppCompat.Dialog">
            activity>
            <activity android:name=".NormalActivity" />
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                intent-filter>
            activity>
        application>
    
    manifest>
    

    其中DialogActivity设置使用的Theme为@style/Theme.AppCompat.Dialog

    有很多系统内置的Theme可供使用, 暂时不做拓展

  2. 对每一个活动状态的函数进行重载 ,输出调试信息:

        /**
         * 在onCreate方法后调用,用于恢复UI状态
         * 从savedInstanceState恢复UI状态
         * 这个savedInstanceState也被传递给了onCreate
         * 自Activity上次可见之后,只有当系统终止了该Activity时,才会被调用
         * 在随后的Activity进程的可见生存期之前被调用
         *
         * @see android.app.Activity#onRestoreInstanceState(android.os.Bundle)
         */
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            Log.i(TAG, "Activity-----onRestoreInstanceState is call start.......");
            super.onRestoreInstanceState(savedInstanceState);
        }
    
        /**
         * 在可见生存期开始时调用
         *
         * @see android.app.Activity#onStart()
         */
        @Override
        protected void onStart() {
            Log.i(TAG, "Activity-----onStart is call start.......");
            super.onStart();
        }
    
        /**
         * 在Activity活动状态生存期开始时调用
         *
         * @see android.app.Activity#onResume()
         */
        @Override
        protected void onResume() {
            Log.i(TAG, "Activity-----onResume is call start.......");
            super.onResume();
        }
    
        /*
         * 把UI状态保存到outState中
         * 如果进程被运行时终止并重启,那么这个Bundle会被传递给onCreate和onRestoreInstanceState
         * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
         */
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            String tempData = "Something you just typed";
            //保存数据:
            outState.putString("data key", tempData);
        }
    
        /**
         * 在Activity活动状态生存期结束时被调用
         * 挂起UI更新,线程或者CPU密集的进程
         */
        @Override
        protected void onPause() {
            Log.i(TAG, "Activity-----onPause is call start.......");
            super.onPause();
        }
    
        /**
         * 挂起UI更新,线程或者处理
         * 当Activity不可见时,保存所有的编辑或者状态改变
         * 调用这个方法后,进程可能会被终止
         */
        @Override
        protected void onStop() {
            Log.i(TAG, "Activity-----onStop is call start.......");
            super.onStop();
    
        }
    
        /**
         * 清理所有的资源,包括结束线程,关闭数据库连接等
         */
        @Override
        protected void onDestroy() {
            Log.i(TAG, "Activity-----onDestroy is call start.......");
            super.onDestroy();
    
        }
    
        /**
         * 当Activity由不可见到可见时调用
         */
        @Override
        protected void onRestart() {
            Log.i(TAG, "Activity-----onRestart is call start.......");
            super.onRestart();
        }
    
  3. 修改onCreate()方法读取信息:

    protected void onCreate(Bundle savedInstanceState) {
            Log.i(TAG, "Activity-----onCreate is call start.......");
        //由于经常出现null, 所以必须要防止
            if (savedInstanceState != null) {
                String savedData = savedInstanceState.getString("data key");
                Log.i(TAG, savedData);
            }
        ...
    }
    

2.5 活动的启动模式:

Android有四种启动活动的模式,分别是standard,singleTop,singleTask,singleInstance

可以在manifest.xml里通过android:launchMode属性来选择启动模式。

standard

standard是活动默认的启动模式,在不进行显式指定的情况下,所有的活动都会自动使用这种启动模式。对于standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动的时候都会创建一个该活动的实例

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Log.d("FirstActivity:", "onCreate: "+this.toString());
        Button button = findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
                startActivity(intent);
            }
        });
    }

在这个当前活动里边启动当前活动,从逻辑来看确实没有什么意义,但是重点是研究standard模式,因此不必在意这段代码的实际用途。我们在oncreate()方法中添加了一条打印语句,用于打印当前活动的实例。我们可以看到打印的信息如图所示:

img

从打印的信息我们可以看出,每点击一次按钮 就会创建出一个当前活动的实例,也就是说,每点击一次就会在返回栈中添加一次实例,点击Back的时候,添加了几次就要点击多少次Back

第一行代码 Ch.2_第11张图片

singleTop

在有些情况下,standard模式不太合理,活动明明已经在栈顶了,为什么再次启动的时候还需要再次创建一个新的活动实例呢? 这只是系统默认的一种启动模式而已,你完全可以更具自已的需求进行修改,比如说使用singleTop模式,当活动的模式指定为singleTop了,在启动活动的时候如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。

修改AndroidMainifest.xml中活动的启动模式,改为singleTop,如下所示

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

然后重新运行程序,查看打印的日志,你会发现只创建了一个活动的实例,每当要启动一个活动时就会判断栈顶的元素是不是要启动的活动,如果是则直接使用栈顶的活动,否则创建新的活动。当要启动的活动未处于栈顶时,还是会创建新的实例的

在这里插入图片描述

第一行代码 Ch.2_第12张图片

singleTask:

使用singleTop模式可以很好的解决重复创建栈顶活动的问题,如但是如第二个所说的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动的实例。那么有没有什么办法让某个活动在应用程序中只有一个实例呢?这里就要用到了singleTask模式,当活动的模式指定为singleTask后,每次启动活动时先会在返回栈中检查是否存该互动的实例,如果有,则使用,并把在这个活动之上的所有互动统统出栈,如果没有则创建一个新的活动实例。

    <activity android:name=".FirstActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>

第一行代码 Ch.2_第13张图片

singleInstance

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

     <activity android:name=".SecondActivity"
            android:launchMode="singleInstance">
          
        activity>

修改SecondActivity的启动模式为singleInstance,然后修改FirstActivity中onCreate()方法中的代码

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        //使用getTaskTId()方法获得当前返回栈back stack的ID
        Log.d("FirstActivity:", "onCreate: "+getTaskId());
        Button button = findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this,FirstActivity.class);
                startActivity(intent);
            }
        });
    }

在onCreate()方法中打印当前返回栈的Id,然后修改SecondActivity中onCreate()方法的代码

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        Log.d("SecondActivity:", "onCreate: "+getTaskId());
        Button button = findViewById(R.id.button_2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
                startActivity(intent);
            }
        });
    }

最后修改ThirdActivity的onCreate()中的代码,如下所示:

public class ThirdActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);
        Log.d("ThirdActivity:", "onCreate: "+getTaskId());
    }
}

同样在onCreate()方法中打印了当前返回栈的id,现在运行程序,在FirstActivity活动中点击按钮进入SecondActivity,在SecondActivity中点击按钮进入ThirdActivity中,查看打印的信息,如图所示:

img

当处于ThridActivity活动中时,点击Back将直接返回到了FirstActivity中,在按下返回键又会返回到SecondActivity中,然后点击Back才会退出程序,原理其实很简单,由于FirstActivity和Thirdactivity是放在同一个返回栈中的,所以第一次点击Back显示的是FirstActivity,当再次点击Back将这时当前返回栈已经空了,于是显示了另外一个返回栈的栈顶活动,即SecondActivity,最后按下Back,则会退出程序,因为所有返回栈都已经空了,所以就退出了程序。

第一行代码 Ch.2_第14张图片

2.6 活动的最佳实践:

这里主要介绍一些使用Activity的技巧:

知晓当前在哪一个活动:

这里主要利用在onCreate()方法中输出当前活动的类名来实现

但是由于对每一个活动的onCreate()方法都添加一个输出有点太麻烦了, 所以采用中间父类的方法, 在中间父类中设置输出类名的代码, 而后所有的自建活动都继承自此父类, 自动获得输出类名的特性

父类AppCompatActivity
中间父类
Activity_1
Activity_2
Activity_3
  • 创建一个中间父类, 继承自自建活动的父类AppCompatActivity, 并加上输出代码:

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

这种思路可以用在很多需要公共代码的地方
如下头的一键退出程序

随时随地的退出程序:

这里主要利用一个自定义的活动管理器, 使用List容器储存活动, 实现add&delete方法用于常规的活动添加与删除, 并创建finishAll()方法用于一次性finish所有的活动

而后不论在程序的何处, 只需要调用finishAll()即可一键退出程序

活动管理器的定义:

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

修改中间父类:

由于仅仅管理活动的增删, 没有涉及具体活动的内容, 所有这里仅仅使用的是类型参数为Activity的ArrayList
并且add & delete都是中间父类指针

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        //将现有活动添加到活动管理器中
        ActivityCollector.addActivity(this);
        //重写onDestory(), 在活动销毁时移除
        @Override protected void onDestroy () {
            super.onDestroy();
            ActivityCollector.removeActivity(this);
        }
    }
}

启动活动的最佳写法:

这里主要解决了活动启动另一个活动时的数据传递问题

之前的向下一个活动中传递数据, 中介绍的是, 使用intent的putExtra()方法把数据塞到intent对象中并启动目标活动
但是, 主要问题是, 这样非常不方便
启动下一个活动时, 无论是参数数量还是数据类型都需要去看其源码

优化的方法是在下一个活动中设置public static 函数用于启动活动实例, 而后启动时仅仅需要向这个方法传递特定的参数即可, 并且还可以根据参数获知数据类型与数量:

有点类似于工厂方法

public static void actionStart(Context context, String datal, String data2) {
    Intent intent = new Intent(context, SecondActivity.class);
    intent.putExtra("paraml", datal);
    intent.putExtra("param2", data2);
    context.startActivity(intent);
}

启动方法:

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

理活动的增删, 没有涉及具体活动的内容, 所有这里仅仅使用的是类型参数为Activity的ArrayList
并且add & delete都是中间父类指针

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        //将现有活动添加到活动管理器中
        ActivityCollector.addActivity(this);
        //重写onDestory(), 在活动销毁时移除
        @Override protected void onDestroy () {
            super.onDestroy();
            ActivityCollector.removeActivity(this);
        }
    }
}

启动活动的最佳写法:

这里主要解决了活动启动另一个活动时的数据传递问题

之前的向下一个活动中传递数据, 中介绍的是, 使用intent的putExtra()方法把数据塞到intent对象中并启动目标活动
但是, 主要问题是, 这样非常不方便
启动下一个活动时, 无论是参数数量还是数据类型都需要去看其源码

优化的方法是在下一个活动中设置public static 函数用于启动活动实例, 而后启动时仅仅需要向这个方法传递特定的参数即可, 并且还可以根据参数获知数据类型与数量:

有点类似于工厂方法

public static void actionStart(Context context, String datal, String data2) {
    Intent intent = new Intent(context, SecondActivity.class);
    intent.putExtra("paraml", datal);
    intent.putExtra("param2", data2);
    context.startActivity(intent);
}

启动方法:

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

你可能感兴趣的:(Android学习笔记)