Android 学习记录一:Activity

LogCat

Android Studio中已经默认添加了LogCat工具,macOS使用control+6,Win使用Alt+6打开。
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下方法:

Log.v() //这个方法打印最为琐碎的,意义最小的日志信息,对应级别verbose
Log.d() //这个方法打印一些调试信息,对应级别debug
Log.i() //这个方法打印一些警告,提示程序的潜在风险,对应级别warn
Log.e() //这个方法用于打印错误信息,对应级别error

尝试

onCreate()方法中添加一行打印日志的语句。
Log.d(“HelloWorldActivity”, “onCreate execute”);
函数传入两个参数,第一个是tag,一般传入当前的类名,主要用于过滤信息,第二个参数是msg,即想要打印的具体内容。

Activity

创建活动

定义一个Activity类的子类,在子类中重写onCreate()方法。

创建和裁剪布局

res/layout目录中新建一个XML布局文件。
尝试添加一个Button

<Button
        android:id="@+id/button_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 1"
        />

其中@+id/button_1是在XML语言中定义一个id的语法。
+去掉,@id/button_1就是引用id的语法。
随后,android:layout_width指定元素宽度,match_parent表示和父元素一样。
android:layout_height自然就是高度,wrap_content表示恰好容纳就可以了。
android:text指定元素中显示的文字内容。

应用布局

onCreate()中写入:setContentView(R.layout.first_layout);传入一个布局文件的id

注册

所有的活动需要在AndroidManifest.xml中注册才能生效。

<activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
                <action
                    android:name=
                        "android.intent.action.MAIN" />
                <category
                    android:name=
                        "android.intent.category.LAUNCHER"/>
            </intent-filter>

可以看到,活动的注册声明要放在<application>标签内,这里是通过<activity>标签来对活动进行注册的。
首先我们要使用android:name来制定具体注册哪一个活动,这里的.FirstActivitycom.example.activitytest.FirstActivity的缩写。由于最外层的<manifest>标签中已经通过package属性指定了用户的包名,因此可以省略。
android:label指定活动中的标题栏的内容。
需要注意的是,给主活动指定的label不仅会成为标题栏,而且还会成为Laucher中应用程序的名称。
<intent-filter>标签,中的<action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER"/>使这个活动成为这个程序的主活动。
如果一个程序没有主活动,那么一般这样的程序都是作为第三方服务供其他应用在内部进行调用的。

隐藏标题栏

只需要在setContentView()方法之前加一句:
requestWindowFeature(Window.FEATURE_NO_TITLE);

在活动中使用Toast

只需要在需要触发Toast的地方加上Toast.makeText方法就行。

Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(
            FirstActivity.this, 
            "You clicked Button 1", 
            Toast.LENGTH_SHORT
         ).show();
     }
});

在活动中使用Menu

先在res中创建menu文件夹,创建一个main.xml布局文件,在XML文件中创建菜单布局。

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/add_item"
        android:title="Add"
        />
    <item
        android:id="@+id/remove_item"
        android:title="Remove"
        />
</menu>

Activity中重写onCreateOptionMenuonOptionItemSelected方法。

public boolean onCreateOptionMenu(Menu menu){
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

public boolean onOptionItemSelected(MenuItem item){
    switch (item.getItemId()) {
        case R.id.add_item:
            Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
            break;
        case R.id.remove_item:
            Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
            break;
        default:
            break;
    }
    return true;
}

销毁一个活动

直接调用finish方法即可。

使用Intent

Intent可以在活动之间跳转。

使用显式Intent

新建一个layout布局文件,在其中创建第二个活动的布局。
AndroidManifest.xml中进行注册。只需要注册一个name即可。
在按钮的点击事件中添加代码:

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

构建一个Intent实例,传入FirstActivity.this作为上下文,传入SecondActivity.class作为活动目标。然后通过startActivity方法来执行这个Intent

使用隐式Intent

隐式方法不明确支出想要启动哪一个活动,而是指定了一系列抽象的actioncategory等信息,由系统分析调用哪个活动。
首先在AndroidManifest.xml中配置<intent-filter>

<intent-filter>
    <action 
        android:name="com.example.activity.test.ACTION_START"
        />
    <category 
        android:name="android.intent.category.DEFAULT"
        />
</intent-filter>

这个action标签指明了当前活动可以响应com.example.activity.test.ACTION_START这个actioncategory标签中包含一些附加信息,更精确地指明了当前活动能够相应的Intent中可能包含category。这两个属性同时匹配时才能相应该Intent
现在改变一下按钮的点击事件。

public void onClick(View v) {
    Intent intent = new Intent(
        "com.example.activity.test.ACTION_START"
    );
    startActivity(intent);
}

新的Intent构造方法传入了一个action字符串,可以直接相应。
这里没有设定category,因为在注册表中注册的是DEFAULT默认,会自动添加到Intent
如果我们给Intent实例添加一个categoryintent.addCategory("com.example.activitytest.MY_CATEGORY");
这样就只能匹配特定的category。同时,给注册表中再添加一个相同的category,这样就能正常运行了。

更多隐式Intent的用法

隐式Intent不仅可以启动自己程序的活动,还可以启动其他应用程序的活动,这使得Android多个应用程序之间的功能共享成为可能。
下面演示用隐式Intent打开系统浏览器。

public void onClick(View v) {
    Intent intent = new Intent(
        Intent.ACTION_VIEW
    );
    intent.setData(Uri.parse(
        "http://wzhzzmzzzy.farbox.com"
    ));
    startActivity(intent);
}

这里指定actionIntent.ACTION_VIEW,这是一个Android系统内置的动作,常量值为android.intent.action.VIEW,然后通过Url.parse方法,解析网址成一个Url对象,调用setDate将其传递进去。

<data>标签

setData方法可以接受一个Uri对象,,指定当前Intent正在操作的数据。通常数据都是以字符串的形式传入到Uri.parse方法中解析产生。
与之对应,我们可以在<intent-filter>标签中再配置一个<data>标签,用于更加精确地指定当前活动能够响应什么类型的数据。下面是<data>标签中可以配置的内容:

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

只有<data>标签中指定的内容和Intent中携带的Data完全一致,当前活动才能相应。只要设置android:schemehttp,就可以相应所有的http协议的Intent了。

向下一个活动传递数据

通过putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动之后,只需要把这些数据再从Intent中取出就可以了。比如说FirstActivity中有一个字符串,现在想把它传递到下一个活动当中:

String s = "Hello SecondActivity";
Intent intent = new Intent(
    FirstActivity.this, 
    SecondActivity.class);
intent.putExtra("extra_data", s);

然后再SecondActivity中取出数据:

Intent i = getIntent();
String s = i.getStringExtra("extra_data");
Log.d("SecondActivity", s);

首先通过getIntent方法获取到用于启动SecondActivityIntent,然后调用getStringExtra方法,传入相应的键值,就可以得到相应的数据了。如果传递int就用getIntExtra方法, 布尔值等以此类推。

返回数据给上一个活动

Activity中有startActivityFroResult方法,也是用于启动活动的,但是这个方法期望在活动销毁时返回一个结果给上一个活动。
该方法接受两个参数,第一个参数的Intent,第二个参数是请求码,用于在之后的回调中判断数据来源。

Intent intent = new Intent(
    SecondActivity.this, ThirdActivity.class
);
startActivityForResult(intent, 1);
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.putExtra(
            "data_return", 
            "Hello FirstActivity");
        setResult(RESULT_OK, intent);
        finish();
    }
});

在活动中构建一个Intent用于传递数据。要传递的数据存放在Intent中,然后调用了setResult方法,这个方法专用于向上一个活动返回数据。
setResult方法有两个参数,第一个用于向上一个活动返回处理结果,一般只用RESULT_OK或者RESULT_CANCELED这两个值,第二个参数将带有数据的Intent传递回去。
由于我们使用startActivityForResult方法,在下一个活动销毁之后会回调上一个活动的onActivityResult方法,因此需要在FirstActivity中重写来得到返回的数据。
onActivityResult方法有三个参数,第一个参数请求码requestCode,第二个参数是返回数据时传入的处理结果resultCode,第三个是携带着返回数据的Intent data
因为一个活动中可能调用startActivityForResult启动很多不同的活动,每个活动返回的数据都会回调,因此需要检查处理结果resultCode来判断数据来源,确定数据是从SecondActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从data中取值并且打印出来。
但是还有一个问题!如果通过Back键来返回,那数据怎么带回来呢?这个时候需要重写一下onBackPressed方法。

@Override
public void onBackPressed(){
    Intent intent = new Intent();
    intent.putExtra("data_return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);
    finish();
}

活动的生命周期

返回栈

Android中的活动是可以层叠的。每创建一个新活动,就会覆盖在原活动之上,点击Back键就会销毁最上面的活动,下面的一个活动就会重新显示出来。
Android是使用任务�(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。每启动一个新活动,就会入栈,每当按下Back或者调用finish(),就会弹栈。每个引用程序都会有自己的返回栈。

活动状态

活动有四种状态:运行、暂停、停止、销毁。
在栈顶时运行、屏幕上可见时暂停、不再处于栈顶时停止、弹栈后销毁。

活动生存期

onCreate() //活动第一次创建时会调用
onStart()  //活动由不可见变为可见是调用
onResume() //活动准备好与用户交互时调用,此时活动位于栈顶
onPause()  //在系统准备去启动或者恢复另一个活动时调用,通常会释放一些占用CPU的数据,保存一些关键数据
onStop()   //活动完全不可见时调用,如果新活动不是对话框式则调用onStop,否则调用onPause
onDestroy()//活动被销毁之前调用,之后活动将变为销毁状态
onRestart()//活动由停止变为运行之前调用

活动被回收了怎么办

如果活动被回收了,而弹栈之后到达了,就会再一次调用活动的onCreate方法,唯一的问题是活动中暂存的临时数据都丢失了。Activity中提供了一个onSaveInstanceState的回调方法,这个方法会保证一定在活动被回收之前调用,因此可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
onSaveInstanceState方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据。每个方法需要传入两个参数,一个是键,一个是真正要保存的内容。

@Override
protected void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
      String tempData = "Somethin";
      outState.putString("data_key", tempData);
}

数据保存之后,onCreate方法中的Bundle参数就会获得这些数据。只需要再次取出即可。

if (savedInstanceState != null){
      String tempData = 
        savedInstanceState.getString("data_key");
    Log.d(TAG, tempData);
}

活动的启动模式

启动模式有四种,standardsingleTopsingleTasksingleInstance。在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。

standard

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

singleTop

singleTop模式下,如果启动活动时发现返回栈栈顶已经是这个活动,则认为可以直接使用它,不会创建新的实例。

singleTask

singleTop模式可以很好地解决重复创建栈顶活动的问题,但是如果该活动没有处于栈顶位置,还是可能会创建多个活动实例。在singleTask下,每次启动该活动室,系统首先会在返回栈中检查是否存在该活动的实例。如果发现已经存在则直接使用该实例,并把在这个活动之上的活动统统出栈,如果没有就创建新的实例。

singleInstance

这个模式算是四种启动模式中最特殊也最复杂的一个了。不同于以上三种,在singleInstance模式下,活动会启用一个新的返回栈来管理(如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。举个例子,假设程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,就可以用singleInstance模式创建一个单独的返回栈来管理这个活动,可以让任意应用程序来调用。

活动的最佳实践

了解当前是哪个活动

新建一个BaseActivity类,重写onCreate方法如下:

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

然后让每个活动类都改为继承自BaseActivity,这样就可以在LogCat中看到当前类名。

随时随地退出程序

当在程序运行中的一个活动,要退出可能会需要按多次Back键,所以最好需要一个直接退出活动的方法。
新建一个ActivityCollector类作为活动管理器:

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

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

BaseActivityonCreate方法中调用ActivityCollectoraddActivity的方法,表明将当前正在创建的活动添加到活动管理器里,然后重写onDestroy方法,并且调用了ActivityCollectorremoveActivity方法,表明将一个马上要销毁的活动从活动管理器中移除。
以后,不管想在什么地方退出程序,只需要调用finishAll就可以了。

启动活动的最佳写法

之前启动的方法是构建一个新的Intent,然后调用startActivity或者startActivityForResult来启动活动。如果SecondActivity需要用到两个字符串参数,在启动时必须要传递,可能会写成下面这样:

Intent intent = new Intent(FirstActivity.this, 
                               SecondActivity.class);
intent.putExtra("extra_data1", s1);
intent.putExtra("extra_data2", s2);
startActivity(intent);

这样虽然可以,但是在SecondActivity是一个黑盒子时,是会出现问题的。
可以修改SecondActivity的代码:

public static void actionStart(Context context, String s1, String s2){
    Intent intent = new Intent(
        context, 
        SecondActivity.class
    );
    intent.putExtra("extra_data1", s1);
    intent.putExtra("extra_data2", s2);
    context.startActivity(intent);
}

这样写的好处是给以后需要启动SecondActivity的活动提供了一个actionStart方法来传入需要的参数,提升了代码的可读性和延续性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,443评论 25 707
  • Day1: 在代码中通过R.string.hello_world可以获得该字符串的引用; 在XML中通过@stri...
    冰凝雪国阅读 1,386评论 0 5
  • 作为Android的四大组件之一,活动最先走进我们的视野,其重要性不言而喻,今天就抽出时间来专门对Android活...
    loser先生阅读 863评论 0 0
  • 17岁那年的雨季,我们有共同的期许。 我是个忘性很大的人,虽然断断续续地一直有记录着自己的成长轨迹,我还是忘记了很...
    小二翻身做掌柜阅读 395评论 0 1
  • 学习心得,或思维冲击点,或是共鸣点。 1、综合性学习中,不能被其他学科吞没,语文味不可丧失。 2、革命传统课文与学...
    鸢尾风铃阅读 417评论 0 1