第一行代码 Ch.2

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:

<?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: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-filter>来筛选

intent匹配模式:

img

实现:

首先在项目的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构造函数的参数

在这里插入图片描述

  • 简要介绍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);
// 直接拨打电话,需要加上权限 <uses-permission id="android.permission.CALL_PHONE" /> 
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);
  • 发送电子邮件
// 给someone@domain.com发邮件
Uri uri = Uri.parse("mailto:someone@domain.com");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
// 给someone@domain.com发邮件发送内容为“Hello”的邮件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "someone@domain.com");
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 = {"1@abc.com", "2@abc.com"}; // 收件人
String[] ccs = {"3@abc.com", "4@abc.com"}; // 抄送
String[] bccs = {"5@abc.com", "6@abc.com"}; // 密送
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中提供了一大堆的重载方法, 可以满足基本需求
并且其中都以键值对的形式储存

在这里插入图片描述

在启动的程序中调用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()方法:

在这里插入图片描述

  • 第一个参数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()函数设置返回的数据:

在这里插入图片描述

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

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

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

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

在这里插入图片描述

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

    通常在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:

在这里插入图片描述

活动状态:

  1. 运行状态

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

  2. 暂停状态

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

  3. 停止状态

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

  4. 销毁状态

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

活动的生存周期:

在这里插入图片描述

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时将数据恢复

在这里插入图片描述

在这里插入图片描述

可以看到, 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<String,Object>
  • Intent负责Activity之间的交互,内部是持有一个Bundle的。

实际测试:

仅记录有用内容

  1. 使用系统内置的Theme:

    Activity注册的.xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <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

在这里插入图片描述

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>

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

在这里插入图片描述

在这里插入图片描述

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>

在这里插入图片描述

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,则会退出程序,因为所有返回栈都已经空了,所以就退出了程序。

在这里插入图片描述

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");
        });
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值