Android基础知识(二)Activity

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhou_xinyu/article/details/81986524

初学Android不久,总结一下杂七杂八的东西,难免说错或者说的不清楚有歧义,希望路过的大佬、中佬、小佬以及和我一样的小白能多多提点。

Activity

最开始成功用Android Studio新建一个Android程序的时候是令人非常激动的φ(>ω<*) (因为新手一开始接触Android Studio的时候的回忆一般总是不太美好的),这个时候刚刚新建出来的程序会给你显示MainActivity(如果你没有自己设定名字的话),大概是长成这个样子的:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

可以看到MainActivit继承了Activity类,并且重写了onCreate方法,此方法为Activity的启动方法,传入了Bundle参数看看前辈有没有什么遗产,然后和布局文件activity_main联系到了一起。Bundle类是在Activity之间传递消息的,这个我们之后再说。在Android Studio里面Ctrl + H可以看到当前类的继承关系。会在屏幕右侧出现一个窗口如下(这是别人GitHub上的一个项目):
继承关系
这里我们需要看到Context类和Activity类,Context类直接继承了Object,Context是啥地位呢?要是在火影里面大概应该叫做大筒木羽衣——六道仙人,Object就是辉夜。当然这只是对于地位的一个类比,实际上我们理解Context的作用的时候应该换一个角度来看。Context的中文意思是“环境、上下文”,很多类都需要Context类型的参数传进构造方法或者是普通的方法中,理解为:我这个类如果要发挥作用是必须依赖于当前所处的环境的,如果没有当前环境对这个类的解释那我就失去了自身的定义

如果不好理解我们再来看个类比:人类是一种社会动物,我们每个人都处于社会之中,扮演儿子、女儿、父亲、母亲、老师、学生、职员、老板等等不同的角色,通过与他人和整个环境之间的联系我们定义自己是谁:在家是儿子、在学校是学生、在单位是职员,那么如果你十分不幸失去了亲人、朋友等等所有与社会的羁绊,那么你就失去了对自身的定位,你一定会迷茫,你不知道自己是谁了,你们可以想象一下在这种情况下自己能撑多久,解决问题的办法之一就是重新去建立与社会环境的联系,认识新的朋友,重新获得自己对于社会的定义。

在代码中给Context的注释是这样写的:Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities,broadcasting and receiving intents, etc.翻译过来差不多是:有关应用程序环境的全局信息的接口。这个抽象类是由Android系统来实现的。它允许访问特定于应用程序的资源和类以及对应用程序级操作(如启动活动)的向上调用,广播和接收intent(意图)等。

接下来我们看看谁继承了Context:
这里写图片描述
这里写图片描述
没错,这就是你能从Android Studio里面获得的答案,但是呢…这特么是个一本正经的错误答案(╯°Д°)╯︵┻━┻!如果你对Android的类库稍微有一点了解的话就应该知道实际上Context应该有三个继承类,而且在很多博客里面出现的都是两个继承类,但是那个MockContext什么鬼的根本没有见过,经常见到的是ContextWrapper和ContextImpl这两个类,从名字上看一个是包装类,一个是实现类,但是为啥子ContextImpl不见了呢?答案是这个文件是保护文件,就是注解了是内部保护文件,所以在Androidstudio中是不显示的。可以去SDK的安装目录中的sources文件夹中直接找那个Java文件,/android-sdk/sources/android-27/android/app/ContextImpl.java。

因为ContextWrapper类和ContextImpl类处于同一包中,所以当调用ContextWrapper类中方法时,无论是通过Context调用,还是在ContextWrapper的子类中调用(如果子类重写没super就不会调用ContextWrapper类中方法了),最终都是调用ContextImpl类中的同名方法。比如启动Activity,在Activity里启动,调用的是Activity类里重写的 startActivity(),如果是在Service里启动,调用的就是ContextImpl类中的 startActivity()。这种模式是装饰模式,Context是抽象构件类,ContextImpl类是具体构件类,ContextWrapper类是抽象装饰类。装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

那么MockContext又是个什么鬼?mock中文意思为模拟,而且这个类位于android.test.mock包里,那么我们就知道这个类与测试有关。当我们要测试一个模块A,他依赖于其它模块B,但是模块B还没实现或现在根本没有,这时就要使用MockContext和其他同样位于android.test.mock包中的类。通过它可以注入其他依赖,模拟Context,或者监听测试的类。

还有一个神奇且蛋疼的问题…Context的数量有多少个?Context一共有Application、Activity和Service三种类型,因此一个应用程序中Context数量的计算公式就可以这样写:Context数量 = Activity数量 + Service数量 + 1
1代表着Application的数量,因为一个应用程序中可以有多个Activity和多个Service,但是只能有一个Application。

简单了解了一下Context,这个东西相当重要,之后还会再说。之后再来看看Activity,在代码中Activity的一开始是这么写的:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback {
    ...
}

我们可以看到继承了ContextThemeWrapper以及实现了乱码七糟一堆的接口,而且代码上面有超大一堆注释!从130行到685行都是注释!!!这个注释在谷歌的官方文档里面都有,一般来说这个注释都非常非常的有用,唯一的问题就是…全特么英文的老子看不懂啊!!!有道等翻译工具可以救你一命…我们一点点靠着有道和谷歌翻译来看看有哪些值得关注的点吧:

  1. Activity类负责创建一个窗口,你可以用setContentView方法来放置UI
  2. activity经常表现为全屏幕的窗口,它们也可以以浮动窗口的形式存在或者嵌入其他的activity
  3. activity从ContextThemeWrapper那里继承了一个theme也就是主题,具体是什么主题请按照如下步骤查看:
    ContextThemeWrapper里面找到getTheme方法 -> getTheme方法里面调用了Resources.selectDefaultTheme方法 ->
    在Resources.selectDefaultTheme中调用了selectSystemTheme方法,其实就在上一个方法的下面,这个方法就是我们要找的方法,如下

    public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
        return selectSystemTheme(curTheme, targetSdkVersion,
                com.android.internal.R.style.Theme,
                com.android.internal.R.style.Theme_Holo,
                com.android.internal.R.style.Theme_DeviceDefault,
                com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
    }
    
    public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
            int dark, int deviceDefault) {
        if (curTheme != 0) {
            return curTheme;
        }
        if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
            return orig;
        }
        if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return holo;
        }
        if (targetSdkVersion < Build.VERSION_CODES.N) {
            return dark;
        }
        return deviceDefault;
    }

    HONEYCOMB代表API11、ICE_CREAM_SANDWICH代表API14、N代表API24,如果都不满足条件则返回的是传进来的默认值。现在(2018年)一般手机都是大于等于API24,所以一般都是那个Light_DarkActionBar。

  4. 在onCreate里面用setContextView和findViewById来初始化布局及其相关控件,在onPause里面提交用户所做更改,经常会将数据交给ContentProvider。
  5. 为了能使用Context.startActivity方法,所有的activity类都要相应的在AndroidManifest.xml里面声明。
  6. 在{@docRoot}guide/topics/fundamentals.html、{@docRoot}guide/components/tasks-and-back-stack.html、{@docRoot}guide/components/activities.html有一些关于activity的详细介绍
    (实际上第一个文件的位置在注释里面写错了,应该是在{@docRoot}guide/components/fundamentals.html里面的)
  7. 在HONEYCOMB也就是API11之后,用Fragment来实现Activity的各种功能更加棒棒,可以十分灵活的应对屏幕大小的变化,并且你的代码也更加清晰。
  8. Activity的生命周期 Activity在操作系统中以一种activity栈的形式被管理这,当一个新的activity开始之后,它会取代之前在栈顶的activity成为运行时的activity,之前的activity则在其下,直到新的activity退出之后才会重新回到栈顶。
  9. 一个activity大致有四种状态:
    activity生命周期
    active 运行态
    paused 暂停态 如果一个活动已经失去焦点,但仍然可见(即一个新的非全屏的或者透明的activity在栈中在你的activity之上)。暂停的活动是完全活着的(保存所有状态和成员的信息,并依附于窗口管理器),但可以被系统在内存非常低的情况下被杀死(每个应用所分配的内存一般情况下有限,不是手机64G、128G内存你的应用都能用,现在的手机型号不同,可以通过Android
    Studio的Device File Explorer来查看你的硬件设备的内部文件,一般这个工具窗口会在Android
    Studio的右下角。在system/build.prop里面按Ctrl + F查找heap就能看到几个匹配选项:

    • dalvik.vm.heapgrowthlimit=256m 未在AndroidManifest.xml的application标签里声明属性android:largeHeap=”true”时的最大可用内存
    • dalvik.vm.heapsize=512m 声明了上述属性之后的最大可用内存
    • dalvik.vm.heapminfree=4m 单次Heap内存调整的最小值。GC相关,主要牵涉性能优化。
    • dalvik.vm.heapminfree=8m 单次Heap内存调整的最大值。GC相关,主要牵涉性能优化。
    • dalvik.vm.heapstartsize=16m 起始分配内存
    • dalvik.vm.heaptargetutilization=0.75 当前理想的堆内存利用率。GC相关,主要牵涉性能优化。

    stopped 停止态 activity完全被挡住,进入停止态,仍然保留着所有的状态和成员信息,然而它对用户不可见了。当其它地方需要内存的时候经常被销毁掉。
    就剩一口气了 当发生了上述内容后系统把activity从内存中回收了,如果它重新出现则需要重新完全的走一遍最开始的过程。也就是在这个时候之前所说的onCreate方法里传进去的Bundle参数才发挥作用,能恢复之前activity用onSaveInstanceState方法(在onPause方法之后onStop之前被调用,如果系统回收了activity则一定会调用这个方法,但是在activity通过返回键或finish方法直接被销毁时不被调用,所以如果要在onCreate里恢复数据要判断一下Bundle是否为null)留下的某些数据。还有onRestoreInstanceState方法也是用Bundle参数恢复数据的,只不过调用发生在onStart之后onResume之前。onRestoreInstanceState(Bundle savedInstanceState)只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。有时候我们需要onCreate()中做的一些初始化完成之后再恢复数据,用onRestoreInstanceState会比较方便,所以才与onCreate在功能上有了重合的地方。

  10. 配置更改 如果你没有你额外指定,那么配置更改(比如屏幕方向,语言,输入设备等)将会造成你现在的activity被destroyed,视情况而定经历正常的onPause,onStop,onDestroy。如果这个activity在前台或者对用户可见,一旦onDestroy被调用那么就会生成一个新的activity的实例,并带着“前辈”实例用onSaveInstanceState创造的savedInstanceState。这么做的原因是因为一个应用的资源(包括layout等)会根据配置的值更改,因此唯一安全的处理配置更改的方法就是重新检索所有资源(layouts、drawables、strings)。因为activity必须知道如何存储他们的状态以及如何根据这些状态重新生成他们自己,那么直接用新的配置重新生成一个activity无疑是个很简单有效的方法。
    在某些特定情况下,你或许不想让你的activity因为一个或几个配置更改而restart。那么首先在manifest里面声明android:configChanges属性,然后覆写onConfigurationChanged处理配置更改,如果某项配置更改发生了,而你又没有在上述方法中做处理,那么还会发生restart。

  11. 启动activity 用startActivity方法启动activity,用Intent类型的参数来描述哪个activity被启动,分为显示和隐示。显示和隐示的区别就是:显示是我就是要找张三这个人,隐示是我要找所有能干这个活的人,具体要哪个人我再自己挑。在Android里面显示就直接启动对应的activity,隐示会给你列出一张表,然后你自己选想要的activity启动。有的时候需要activity在结束的时候返回数据,就是父窗体与子窗体之间的数据传输,这种情况在几乎所有类别的应用开发里都存在。在这种情况里用startActivityForResult(Intent, int requestCode)方法启动,用onActivityResult(int requestCode, int resultCode, Intent)方法接受返回的数据。在被启动的相当于子窗体的activity中用setResult(int resultCode, Intent)方法来返回数据给父窗体。来个栗子,A为父窗体activity,B为子窗体activity:
    A.startActivityForResult(Intent,9527)——>B.setResult(666, Intent)——>A中

        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (resultCode == 666) {
                if (requestCode == 9527) {
                    int three = data.getIntExtra("three", 0);
                }
            }
        }

    requestCode和resultCode是为了让父窗体知道“我叫了谁,是谁鸟的我”。如果子窗体activity没有返回应该返回的数据,父窗体收到的resultCode为RESULT_CANCELED。

  12. 保存持久化状态 一般来说有两种持久化状态要处理:一种是类似于文档型的共享数据(一般是用ContentProvider存在SQLite数据库里面),另一种是内部状态比如说用户首选项。(这里原文是这样的There are generally two kinds of persistent state than an activity will deal with: shared document-like data (typically stored in a SQLite database using a {@linkplain android.content.ContentProvider content provider}) and internal state such as user preferences.我个人认为那个than可能that是打错了,要不就是我不懂的一种语法,有大佬知道的话还希望能告诉一下)
    对于content provider数据,我们建议activity使用“就地编辑”用户模型。也就是说,用户进行的任何编辑都可以立即有效地完成,而不需要额外的确认步骤。支持这种模型通常只需遵循两个规则即可:

    • 在创建新文档时,将立即为其创建备份数据库条目或文件。例如,如果用户选择写一封新电子邮件,那么一旦他们开始输入数据,就会为该电子邮件创建一个新的条目,这样,如果他们在这之后再去其他活动,该电子邮件现在就会出现在草稿列表中。
    • 当一个活动的onPause()方法被调用时,应该将用户所做的任何更改提交给备份的content provider或者文件。这可以确保即将运行的任何其他活动都可以看到这些更改。

    此模型旨在防止用户在活动之间进行导航时出现数据丢失,并允许系统在暂停活动之后的任何时候安全地终止活动(因为在其他地方需要系统资源)。注意,这意味着用户从activity中按下BACK并不意味着“取消”——它意味着保留活动的当前内容。取消活动中的编辑必须通过其他机制提供,例如显式的“恢复”或“撤消”选项。
    activity的持久化状态由getPreferences方法管理,这个方法允许你修改一个与当前activity相对应的键值对。如果要使用跨多个应用程序组件(activity,service,broadcast receiver,content provider)共享的preference,可以使用底层的Context.getSharedPreferences()方法来检索存储在特定名称下的preference对象。(不能跨应用程序,如果跨应用则需要content provider)。下面是个栗子:

    protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
    
          SharedPreferences mPrefs = getSharedPreferences();
          mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
      }
    
      protected void onPause() {
          super.onPause();
    
          SharedPreferences.Editor ed = mPrefs.edit();
          ed.putInt("view_mode", mCurViewMode);
          ed.commit();
      }
  13. 许可 当你想用content provider的时候标准的许可声明可能不太够用,典型示例是邮件应用程序中的附件。应该通过权限来保护对邮件的访问,因为这是敏感的用户数据。但是,如果图像附件的URI被提供给图像查看器,则该图像查看器将无权打开附件,因为它没有理由拥有访问所有电子邮件的权限。此问题的解决方案是per-URI权限:启动activity或将结果返回给activity时,调用者可以设置 Intent.FLAG_GRANT_READ_URI_PERMISSION或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这授予了activity一个权限访问Intent中的特定数据URI,无论它是否具有访问与Intent对应的content provider中的数据的任何权限。这种通用的功能样式模型能够临时授予细粒度的许可,这种方式可以成为减少应用所需许可的关键。授予细粒度URI权限需要与持有这些URI的content provider进行一些合作。强烈建议用content provider实现此功能,并通过声明android:grantUriPermissions属性或 grant-uri-permissions标签支持这种功能 。

好了,以上大概就是注释的主要内容,如果有翻译的影响理解的地方还希望有人指点,英语在各方面的学习中都很重要,因为咱们学的大多数的东西都是国外比较强(˘•ω•˘)。如果你学的很吃力可以把一部分锅甩给英语不行上。个人认为现在离深度学习等技术消除全世界语言障碍还有至少五到十年左右,有时间还是稍微学习一下英语吧。

猜你喜欢

转载自blog.csdn.net/zhou_xinyu/article/details/81986524