Android第一行代码(第二版)读书笔记1

第二章 探究活动

2.2 活动的基本用法

手工建立Activity

新建项目时选择No Activity,在res文件夹下新建layout.在新建Activity选择Empty Activity.不要勾选Generate layout file,后续使用手工关联layout

  • 建立Resource Layout
  • 建立Activity类(Empty Activity),重写onCreate方法,并调用Layout
  • 在AndroidManifest中注册Activity
    AndroidManifest中内容如下:
 <activity android:name=".FirstActivity"
    android:label="This is the FirstActivity">
     <intent-filter>
         <action android:name="android.intent.action.MAIN"></action>
         <category android:name="android.intent.category.LAUNCHER"></category>
     </intent-filter>
 </activity>

FirstActivity.java如下

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

布局略

Toast

Toast是Android系统中的提醒方式,程序可以将短小的消息通知给用户并在一段时间后自动消失.弹出Toast的方式如下,修改FirstActivity.java

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1 = findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(FirstActivity.this,"You click Button 1",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

如上所示,findViewById()返回button对象,通过setOnClickListener()注册一个监听器,并执行onClick方法.
Toast通过makeText方法创建Toast对象,并调用show()显示出来.makeText方法的三个参数分别为:当前的Context;消息内容;显示时间的长短.

在res下新建Android Resource Directory,选择menu,并其名称为menu.在menu下新建Menu Resource File,起名为main.xml.在main.xml中定义Menu的Item.如下所示:

<?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>
    <item
        android:id="@+id/remove_item" android:title="Remove"></item>
</menu>

在FirstActivity.java中重写onCreateOptionsMenu()方法,

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

通过getMenuInfater()方法可以获取MenuInflater对象,通过inflate方法给当前资源创建菜单.inflate方法中第一个参数是获取资源文件中的menu定义,第二个参数指定菜单项目将添加到哪个menu对象中.返回true标识允许菜单被显示出来.
初始化菜单后,需要为每个菜单定义响应事件,此时需要重写onOptionsItemSelected()方法,在FirstActivity中重写此方法,代码如下:

  @Override
   public boolean onOptionsItemSelected(@NonNull MenuItem item) {
       switch (item.getItemId()){
           case R.id.add_item:
               Toast.makeText(this,"You click add",Toast.LENGTH_SHORT).show();
               break;
           case R.id.remove_item:
               Toast.makeText(this,"You click Remove",Toast.LENGTH_SHORT).show();
               break;
           default:
       }
//        return super.onOptionsItemSelected(item);
       return true;
   }

此时重新编译后,点击右上角菜单即可看到响应结果.完整的FirstActivity.java代码如下:

public class FirstActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button1 = findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(FirstActivity.this,"You click Button 1",Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.add_item:
                Toast.makeText(this,"You click add",Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this,"You click Remove",Toast.LENGTH_SHORT).show();
                break;
            default:
        }
//        return super.onOptionsItemSelected(item);
        return true;
    }

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

销毁活动

在终端可以使用Back键来销毁当前活动,也可以使用Activity类中的finish()方法来销毁活动.只需要在监听事件或其他需要的地方调用finish()方法即可.在FirstActivity.java中添加一个按钮并监听click,调用finish().在布局中增加一个Button,id为button1Finish.
在onCreate中增加代码如下:

 Button button1Finish = findViewById(R.id.button1Finish);
 button1Finish.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           finish();
           Log.d("FirstActivity","Finish--");
       }
   });

2.3 使用Intent在活动间穿梭

显式Intent

Intent 是一个消息传递对象,您可以用来从其他应用组件请求操作.尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

  • 启动 Activity
  • 启动服务
  • 广播传递

Intent大致可分两类:显示Intent和隐式Intent.

按照之前的FirstActivity方式建立SecondActivity.java及响应的布局文件,布局文件起名为second_layout.注意不要勾选Launcher Activity选项.替换second_layout.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/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="BUTTON 2"/>
</LinearLayout>

此时androidManifest文件中会自动注册SecondActivity,修改FirstActivity中的button1的监听,如下

 @Override
 public void onClick(View view) {
      Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
      startActivity(intent);
  }

这里首先构建出了一个Intent,传入FirstActivity.this作为上下文,传入SecondActivity.class作为活动目标,通过startActivity来执行Intent.

隐式Intent

隐式需要在androidManifest中对应的activity定义intent-filter标签内指定响应的action与category,首先在SecondActivity的activity标签中声明一个action和category,如下所示:

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

修改FirstActivity中Button的监听,如下

button1.setOnClickListener(new View.OnClickListener() {
  @Override
     public void onClick(View view) {
         Intent intent1 = new Intent("com.example.secondeActivity.ACTION_START1");
         startActivity(intent1);
     }
 });

在使用隐式Intent时,必须action与category同时匹配上才会响应,由于android.intent.category.DEFAULT时一种默认的category,所以上面例子中在调用时没有指定category依然能够运行.

修改FirstActivity中Button的监听,如下

 button1.setOnClickListener(new View.OnClickListener() {
    @Override
     public void onClick(View view) {
         Intent intent1 = new Intent("com.example.secondeActivity.ACTION_START1");
         intent1.addCategory("com.example.activityIntentCategory.Category1");
         startActivity(intent1);
     }
 });

此时因为在androidManifest中SecondActivity的activity没有指定com.example.activityIntentCategory.Category1,所以此时代码运行会报错,提示No Activity found to handle Intent.修改SecondActivity的activity中的intent-filter如下:

<activity android:name=".SecondActivity">
  <intent-filter>
        <action android:name="com.example.secondeActivity.ACTION_START1"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
        <category android:name="com.example.activityIntentCategory.Category1"></category>
    </intent-filter>
</activity>

此时在进行调用就一切正常了

更多隐式Intent

在Intent中启动系统浏览器.在SecondActivity.java的Button中增加监听,调用系统的浏览器,代码如下:

 Button button2 = findViewById(R.id.button2);
 button2.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           Intent intent = new Intent(Intent.ACTION_VIEW);
           intent.setData(Uri.parse("http://baidu.com"));
           startActivity(intent);
       }
   });

这里的Intent.ACTION_VIEW是安卓的内置动作,对应的常量如下

 public static final String ACTION_VIEW = "android.intent.action.VIEW";

Uri.parse("http://baidu.com")将网址的字符串解析成Uri对象,通过setData方法传递.也可以在intent-filter标签中配置一个data标签,用来更精确的指定当前活动能够响应什么类型数据.data标签主要配置的内容如下:

  • android:scheme .用来指定数据的协议
  • android:host .用来指定数据的主机部分,如www.xxx.xx
  • android:prod .用来指定数据的端口
  • android:path .用来指定主机名和端口之后的内容
  • android:mimeType .用来指定处理的数据类型,可以使用通配符
<data android:scheme="string"
          android:host="string"
          android:port="string"
          android:path="string"
          android:pathPattern="string"
          android:pathPrefix="string"
          android:mimeType="string" />

需要注意的是向 Intent 过滤器添加数据规范.该规范可以是只有数据类型(mimeType 属性),可以是只有 URI,也可以是既有数据类型又有 URI.URI 由其各个部分的单独属性指定:

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

用于指定网址格式的以下属性是可选的,但也相互依赖:

  • 如果没有为 Intent 过滤器指定 scheme,则系统会忽略其他所有 URI 属性.
  • 如果没有为过滤器指定 host,则系统会忽略 port 属性以及所有路径属性.

比如,如果需要对网址的port过滤,则必须有sheme与host属性的配置.

建立一个ThirdActivity.java来测试指定http类型是数据响应.新建Empty Activity起名为ThirdActivity,对应生成的布局起名为third_layout.其中布局内容如下:

<?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">
    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="BUTTON 3" />
</LinearLayout>

AndroidManifest.xml中修改ThirdActivity的注册信息,内容如下:

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

这里配置了intent-filter,指定的Intent能够响应的action为Intent.ACTION_VIEW,并指定默认的category.在配置此处是,开发工具(Android Studio提示需要增加tools:ignore="AppLinkUrlError")

此时重新运行程序,系统自动弹出列表,显示能够响应这个Intent(Intent.ACTION_VIEW)的所有程序.所以.如果选择使用浏览器打开,则此时会默认启动系统内置浏览器.如果选择使用ThirdActivity来响应Intent,虽然可以响应这个活动,但并不会加载任何网页.在上述配置中,可以将data标签中android:scheme的值就改为ftp,此时ThirdActivity则不会响应这个Intent.

测试网址的过滤,将AndroidManifest.xml中修改ThirdActivity的注册信息,内容如下:

  <activity android:name=".ThridActivity">
     <intent-filter tools:ignore="AppLinkUrlError">
           <action android:name="android.intent.action.VIEW"></action>
           <category android:name="android.intent.category.DEFAULT"></category>
           <data android:scheme="http" android:host="www.baidu.com" android:port="9999"/>
       </intent-filter>
   </activity>

在SecondActivity.java中将button修改如下:

 button2.setOnClickListener(new View.OnClickListener() {
  @Override
       public void onClick(View view) {
           Intent intent = new Intent(Intent.ACTION_VIEW);
           intent.setData(Uri.parse("http://baidu.com:80"));
           startActivity(intent);
       }
   });

此时,ThirdActivity不会对此Intent进行响应.

调用拨号的Intent示例,按钮监听部分如下:

 button.setOnClickListener(new View.OnClickListener() {
    @Override
     public void onClick(View view) {
         Intent intent = new Intent(Intent.ACTION_DIAL);
         intent.setData(Uri.parse("tel:10086"));
         startActivity(intent);
     }
 });

通用常见的Intent在官网文档中有具体的描述

向下一个活动传递数据

在Intent中提供了一些列的putExtra()方法的重载,可以将传递的数据放到Intent中.修改FirstActivity中Button的监听方法,使用Intent显示的方式传递数据.代码如下:

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

在SecondActivity中使用getIntent()方法来获取Intent并将传递的值取出来,代码如下:

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

返回数据给上一个活动

在Activity中使用startActivityForResult()方法可以在活动销毁时返回一个结果给上一个活动,此方法接受两个参数,第一个参数是Intent,第二个参数是请求码(requestCode),修改FirstActivity中的监听,使用startActivityForResult方法来启动SecondActivity,代码如下:

 button1.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View view) {
//      下一个活动返回数据
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivityForResult(intent, 1);
    }
});

在SecondActivity中,修改监听方法,创建一个新的Intent并设置返回值,代码如下:

button2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent1  = new Intent();
        intent1.putExtra("data_return","Hello FirstActivity This is Second Activity");
        setResult(RESULT_OK,intent1);
        finish();
    }
});

上面代码中,先创建了一个Intent来传递数据,然后调用setResult方法来返回处理结果,setResult()方法的第一个参数用于向上返回处理结果,一般使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数是将Intent传递回去,最后调用finish方法销毁活动.
在FirstActivity中,需要重写onActivityResult方法来获取返回的数据,代码如下:

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

            break;
        default:
    }
}

onActivityResult中的三个参数,其中requestCode为startActivityForResult()方法带过来的唯一键,resultCode为SecondActivity中setResult方法设置的返回值,Intent为SecondActivity中返回的Intent.此时如果用户使用返回按钮来结束活动时,需要在SecondActivity中重写onBackPassed()方法来解决此问题,并通过onBackPassed()方法中来返回数据,修改SecondActivity中代码,增加onBackPassed方法:

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

2.4 活动的生命周期

2.4.1 返回栈

Android使用任务Task来管理活动,一个任务是一组存放在栈里的活动集合,这个集合叫做返回栈.档一个活动入栈后,调用finish方法后最先出栈.

2.4.2 活动状态

  • 运行状态:此时活动处于栈顶
  • 暂停状态:活动不处于栈顶,当内存极低情况下,系统才会考虑回收
  • 停滞状态:活动不处于栈顶,完全不可见,进入停止状态,可能被系统回收
  • 销毁状态:活动出栈,系统回收

2.4.3 活动的生命周期

  • onCreate() 活动第一次被创建时候调用,所有的初始化操作在此方法内完成
  • onStart() 活动由不可见到可见时被调用
  • onResume() 方法在活动准备好和用户进行交互时候调用,此时活动一定处于返回栈的栈顶,并处于运行状态
  • onPause() 方法在系统准备去启动或者恢复另一个活动时候调用.通常在这个方法中将一些消耗CPU的资源释放掉并保留一些关键数据,但此方法执行速度要快,不然会影响新的栈顶的活动.
  • onStop() 方法在活动完全不可见时候调用,和onPause()方法的区别是如果启动的新活动是一个对话框式活动,那么onPause()方法会被调用,而onStop()方法不会被执行
  • onDestroy() 方法在活动被销毁前调用,之后活动变为销毁状态
  • onRestart() 方法在活动由停止变为运行之前调用,也就是活动被重新启用了
    以上7个方法中,除了onRestart()方法,其他的都是两两相对的,可分为3种生存期:
  • 完全生存期.活动在onCreate()和onDestroy()之间经历的
  • 可见生存期.活动在onStart()和onStop()之间经历的
  • 前台生存期.活动在onResume()和onPause()之间经历的

2.4.5 活动被回收

onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供一列的方法保存数据,比如在MainActivity中重写此方法,将参数通过Bundle的putString()方法将值保存.MainActivity中代码如下

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Temp Data";
    outState.putString("temp_key",tempData);
}

在onCreate时获取如下:

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

2.6 活动的最佳实践

参考书中例子,完成BaseActivity类及ActivityCollector类,实现获取当前活动及销毁所有活动.
BaseActivity.java

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

ActivityCollector.java

public class ActivityCollector {
    public static List<Activity> activityList = new ArrayList<>();

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

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

    public static void finishAll() {
        for (Activity activity : activityList) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

个人测试的Demo地址均放在了github上地址链接

原文地址:https://www.cnblogs.com/GYoungBean/p/13204049.html