Activity的生命周期

时间过得飞快一转眼一年时间就过去了,先小小的感叹下啊。回到正题,最近在投简历看看是否有更好的机会和平台,很高兴得到了爱奇艺的面试机会。在准备期间发现自己虽然平时输入了很多但都没有整理时间一久都忘记了(过完年感觉都就着饭消化了)。还有发现其实很多Android的基础知识并不是很深入细节的了解,所以决定坚持把学到的知识点进行总结,与大家共同进步。

首先先从最基本Android的四大组件:

  • Activity
  • Service
  • Broadcast receiver
  • Content provider

首先我就从第一个Activity开始学习总结,Activity被中文翻译成“活动”,但我觉得《Android开发艺术探索》的作者任玉刚翻译的更贴切,他翻译成“界面”。接下来的知识点总结其实也是基于此书的内容。Activity是我们Android开发人员最熟悉的组件了(不熟悉的请拖出去。。。)在我们的日常开发中每个UI界面就可以简单的理解成一个Activity。关于它的技术点其实有很多值得总结的,此篇文章只针对它的生命周期进行总结。

1.1 Activity的生命周期分析

1.1.1 简述生命周期方法

在正常情况下Activity的创建到销毁会经历如下的生命周期。

  1. onCreate() :首次创建Activity时调用,这是生命周期的第一个方法。在这里你可以做一些静态设置,如:调用setContentView()方法来设置视图、该Activity所需要的一些初始化操作等。该方法会传入一个Bundle对象,其中包含Activity的上一状态信息,不过前提是捕获了该状态,后续也会提及此知识点。它始终后接onStart()。

  2. onRestart():在Activitie已停止后再次启动前调用,表示Activity正在重新启动。一般情况下当Activity从不可见状态变为可见状态时,onRestart()就会被调用。换一种更专业的说法既是当前Activity处于非栈()顶状态又再次回到栈顶状态时,此时就会触发此方法。这种情形一般都是用户行为操作导致的,如用户在当前界面按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会处于后台不可见状态,也就是onPause()和onStop()被执行了,但并没用执行onDestroy(),接着用户又回到了这个Activity,此时就会调用onRestart()。它始终后接onStart()。

  3. onStart():在Activity即将对用户可见之前调用。此时Activity对用户还不可见不可以进行交互。如果Activity转入前台,则后接onResume(),如果Activity转入隐藏状态,则后接onStop()。

  4. onResume():表示Activity已经处在栈顶,并且捕获用户的所有输入,可以与用户进行交互。这里注意这个点"可以与用户交互",这也是与onStart()方法最大的区别。onStart与onResume都表示Activity已经可见,但是onStart的时候相对用户还是处于后台,并不能与用户进行交互。可以简单的总结成,onStart表示可见但不能与用户交互;onResume表示即可见又可以跟onStart进行交互。

  5. onPause():表示用户正在离开当前Activity,Activity失去焦点并进入暂停状态。通常情况下接下来onStop方法会被调用,但有些极端情况下,如果当前快速地再回到当前Activity,那么onResume会被调用。这是一种极端情况,用户操作很难重现这一场景。这里官方建议做一些轻量级的释放资源的操作,不能做太耗时的操作,因为这会影响到新Activity的显示,onPause必须先执行完毕,新Activity的才会开始创建并执行到新的Activity的onResume状态。
    这里还有提一下官方列举了进入onPause状态的情景:

    • 一些阻碍APP运行的事件发生时,这也是最通常的情况。
    • 再Android7.0(API 24)或更高版本时,多个APP运行在多个窗口中。此时系统会给不处于焦点状态的Activity调用此方法。
    • 开启一个半透明的Activity(如一个只包含Dialog的Activity),当前Activity部分可见并没有焦点,此时当前Activity处于onPause状态。这里强调一下开启一个新的半透明的Activity,之前我的理解有偏差,以为只要当前界面开启了一个Dialog,当前Activity就会处于onPause状态,其实并不是。

    关于这点网上也有很多解释的文章,大体意思既是只有当前Activity不再处于Activity栈的栈顶时才会被调用。Dialog本身不是一个Activity而需要依附在Activity上,所以它并不能引起Activity栈的变化即不能触发onPause状态。但这里还有一种常见的dialog功能实现的方法,即是用一个半透明背景的Activity来实现dialog效果。此时相当于开启了一个新的Activity,导致当前Activity并在处于栈顶,所以就会触发onPause。

  6. onStop(): 表示当前Activity不再显示给用户,即将终止。这里可以做一些稍微重量级的操作,但同样也不能太耗时。同时官方文档里有这样一句话:

    It is also important that you use onStop() to release resources that might leak memory, because it is possible for the system to kill the process hosting your activity without calling the activity's final onDestroy() callback.

    翻译过来就是你应该在onStop方法内完成可能造成内存泄漏的资源释放,因为当系统去杀掉你Activity所在的进程时,可能最终不会调用onDestroy方法。

  7. onDestroy(): 表示该Activity即将被销毁。这是Activity生命周期中的最后一个回调,在这里我们可以释放onStop中还未释放的资源的操作。

正常情况下Activity只会经历以上的生命周期回调,下面附一张官网的图片,清晰地展示了各生命周期方法的流程:

Activity生命周期方法流程图

以上就是Activity正常生命周期状态变化时所需要回调的方法。但有时系统会经历一些特殊情况,如内存不足、系统资源配置发生改变等,下面我们就来一起学习异常情况下Activity生命周期是如何切换的。

1.1.2 异常情况下的生命周期分析

1. 情况1:资源相关的系统配置发生改变导致Activity被杀掉并重新创建
这里提到的系统配置发生改变包括很多种情况,如常见的屏幕旋转、系统改变语言、Android 7.0以上系统多窗口切换等。当这些系统配置情况改变时,Activity就会被销毁后重建。

在默认情况下,如果我们的Activity不做任何处理,那么当系统配置发生改变后,它的生命周期就会如下变化:


异常情况下Activity的生命周期切换过程

当系统配置发生变化的时候Activity会被销毁重建,也会调用onPause、onStop、onDestroy这三个生命周期方法。同时因为Activity是在异常情况下停止的,系统还会调用onSaveInstanceState。关于这个方法官网是如下介绍的:

The onSaveInstanceState() callback is designed to store relatively small amounts of data needed to easily reload the state of UI controller, such as an activity or a fragment, if the system stops and later recreates that controller.

翻译一下即是,onSaveInstanceState()方法是在被设计成保存相对小数据量的用于恢复UI控制器(如Activity、Fragment)的状态的数据。它会在系统停止UI控制器并且稍后要重新创建UI控制器时调用。同时官网也表明了该方法被调用的情景:

  • 在系统内存受限时,系统会杀掉处于后台的APP进程
  • 当系统配置发生变化时,如屏幕旋转或者输入语言变化

所以这里需要强调一点,正常情况下Activity并不会回调这个方法(例如用户点击back键finish掉当前Activity),只有当Activity被异常终止时才会回调这个方法。这个方法的调用事件在onStop之前,它与onPause没有对应的时间关系。它内部通过Bundle对象来存储简单的数据,并在重建时将该Bundle对象同时传递给onCreate和onRestoreInstanceState。我们择其一,在重建时进行恢复操作。二者的区别是:

  1. 调用时机不同,onCreate是重建时第一个调用的方法,而onRestoreInstanceState会在onStart后进行回调。
  2. 恢复方式不同,onCreate方法的Bundle参数可能为null,所以使用时需要加入判断代码。而onRestoreInstanceState一旦调用就代表Activity被异常关闭的,调用过了onSaveInstanceState方法,所以onRestoreInstanceState方法的Bundle参数一定不为null,不需要判断可以读取恢复数据。

此外,还需要注意onSaveInstanceState和onRestoreInstanceState两个方法,必须总是调用superclass的同名方法。因为系统默认为我们做了一些保存和恢复的操作,比如文本框输入的数据,列表停留的视图位置等。这里需要知道每个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,具体实现的话各位看官可以自行查看下。关于View恢复的流程是这样的:Activity被意外关闭 ,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,Window接着会委托它上面的顶层容器去保存数据,一般情况下就是DecorView。最后顶层View会继续再去一一通知它的子元素来保存数据。这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情,在Android中很多应用,如事件分发机制、View绘制机制等。数据恢复的流程与保存流程也是类似的。这里还需要重点提一下关于View的自动恢复状态的条件,其中一个就是必须设置唯一的ID值。大家请看View源码中的dispatchSaveInstanceState()方法:

  protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                container.put(mID, state);
            }
        }
    }

这里mID即是我们通过xml布局中android:id属性赋值或调用setId()方法赋值的。大家可以清楚的看到如果mID的值为NO_ID时,就不会调用container.put(mID,state)这条语句。这里的container类型为SparseArray<Parcelable>即是用mID为key,上一级传入的包含View状态的Parcelable对象为value。

2. 情况2:资源内存不足导致低优先级的Activity被杀死

此种情况Activity的数据存储和恢复过程和情况1完全相同。这里要提一下Android系统中一个不寻常的一点,一个程序的进程生命周期并不由程序本身直接管理,而是由系统决定。系统建立了一套进程优先级制度来管理各个进程,所以这也是需要我们开发人员注意学习的地方。其中官网就列出了一个比较容易出错的点:

A common example of a process life-cycle bug is a BroadcastReceiver that starts a thread when it receives an Intent in its BroadcastReceiver.onReceive() method, and then returns from the function. Once it returns, the system considers the BroadcastReceiver to be no longer active, and thus, its hosting process no longer needed (unless other application components are active in it). So, the system may kill the process at any time to reclaim memory, and in doing so, it terminates the spawned thread running in the process. The solution to this problem is typically to schedule a JobService from the BroadcastReceiver, so the system knows that there is still active work being done in the process.

翻译过来即是,很容易出bug的点即是在BroadcastReceiver.onReceive()方法内部开启一个线程去执行一个耗时操作时,因为BroadcastReceiver的生命周期很短,当onReceive()方法运行完成后系统就认为BroadcastReceive不再活动,所以在它的进程(如果没有其他程序组件在运行)就会随时被系统回收,导致你的耗时操作根本不能运行出结果。所以官方也给出了解决方式:在BroadcastReceiver中开启一个JobService,这样当onReceive()方法结束后,进程中仍然有Service组件在运行,进程优先级较高,系统就不会轻易收回此进程了。

好我们来总结一下Android系统对进程优先级制度是如何制定的,优先级顺序由高到低:

  1. 前台进程:优先级最高,具备下列条件的进程会被定义为前台进程:
  • 一个正在运行在屏幕上的Activity,用户正在与它进行交互。(也就是说它的onResume方法被调用)。
  • 当一个BroadcastReceive正在运行时,即是它的onReceive方法正在被在被执行时。
  • 当一个Service正在执行回调方法时,即是它的onCreate()、onStart()、onDestroy()正在被调用时。

当内存过低时,前台进程作为最后一个被系统回收的进程。或者当前台进程本身已经无法继续运行时,也会被系统回收。

  1. 可见进程: 优先级次之,系统结束此类进程也会给用户带来明显的负面影响。它的条件是:
  • 一个对用户可见的Activity,但未处于前台(如该Activity正处于onPause状态)。通常发生的情况是当一个前台Activity显示一个dialog,而之前的Activity处于它的后边同样能够被用户看到。另一个例子是当你调用运行时权限对话框时。
  • 当一个Service作为一个前台Service在运行,通过调用它的startForeground()方法。
  • 一个绑定到可见或处于前台Activity的Service,如:动态壁纸Service、输入法Service等。

但请记住虽然前台进程不易被杀掉,但如果前台进程对内存压力过大,可见进程仍有可能被系统杀掉。举例当前Activity背后可见的Activity被杀掉后,对用户来说将看到一个黑屏背景。所以你需要在Activity的onSaveInstanceState()和onRestoreInstanceState()两个方法来做存储和恢复界面的操作,及时的弥补这个问题。

  1. 服务进程:一个Service通过startService()方法被开启。通常就是指那些在后台做下载或上传操作的Service。错非内存无法保证前台进程和可见进程的运行,才会杀掉服务进程。这里需要注意,当一个服务进程运行过长(如30分钟或更久)系统会降低它的重要级别。把它放入缓存进程中,这是为了避免一个长时间运行的服务导致内存泄漏或消耗过大的问题。

  2. 缓存进程: 当前不被需要的,系统可以在需要时随时杀掉的进程。一般场景下,设备上的许多内存就是用在这上面的,让你重新回到之前打开过的某个 Activity 。Activity不是为了被杀而创建的(记住重新创建需要代价的),所以这些进程会保留一段时间,按照最近最少使用的原则回收。

最后,一个进程的优先级可能会被提高基于它所依赖的其他更高优先级的进程。例如当一个进程A绑定了一个运行在B进程的Service设置flag参数为Context.BIND_AUTO_CREATE,此时B进程拥有跟A进程一样的优先级。

从上面的一系列总结也可以了解到,当一个进程中没有Android四大组件运行,它的优先级为最低的,随时可能被系统回收。

我们再来看看Activity的优先级情况排序:

  1. 前台Activity: 正在和用户进行交互,处于onResume和onPause之间。
  2. 可见但非前台Activity: 比如上面提到的弹出运行时权限对话框,处于onPause。
  3. 后台Activity: 已经被切换的Activity,处于onStop。

3. 系统配置改变的应对方法

我们知道当系统配置发生改变后,Activity会被重新创建。那如何阻止重新创建呢?我们可以在AndroidManifest清单文件中通过设置Activity的configChanges属性来阻止系统的默认处理方式。比如我们想让屏幕旋转时不重新创建Activity,只需将configChanges属性添加orientation这个值。如:

android:configChanges="orientation"

如果我们想指定多个值,可以用"|"连接起来。比如:android:configChanges="orientation|keyboardHidden"。关于这个属性的配置值请看下图:

configChanges相关值

这里官方关于配置屏幕旋转情况有一个特别提醒如下:

注意:从 Android 3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。因此,在开发针对 API 级别 13 或更高版本(正如 minSdkVersiontargetSdkVersion 属性中所声明)的应用时,若要避免由于设备方向改变而导致运行时重启,则除了 "orientation" 值以外,您还必须添加 "screenSize" 值。 也就是说,您必须声明 android:configChanges="orientation|screenSize"。但是,如果您的应用面向 API 级别 12 或更低版本,则 Activity 始终会自行处理此配置变更(即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。

具体的测试代码我就不写了,这里需要说明的是当设置了相关属性值以后。系统配置再发生相应变化时,将不再调用onSaveInstanceState和onRestoreInstanceState两个方法,取而代之的是调用了Activity的onConfigurationChanged方法。这个时候我们可以在这里做一些特殊处理。

写到这我要说的是,以上配置变化的应对方式是我们开发当中常见的处理方式。但官方并不建议开发人员这样处理:

注:自行处理配置变更可能导致备用资源的使用更为困难,因为系统不会为您自动应用这些资源。 只能在您必须避免 Activity 因配置变更而重启这一万般无奈的情况下,才考虑采用自行处理配置变更这种方法,而且对于大多数应用并不建议使用此方法。

其实关于为什么官方不建议采用此种方式的原因,我也搜索到了一个开发者的看法。其中一位的观点我觉得比较容易理解。他的观点如下:

当用户离开应用,在回到应用前被销毁的话,例如点击了屏幕的Home键或者有个电话打进来,用户很久之后才回到应用程序,但是在此之前系统因为资源紧张而销毁了应用进程,当用户返回还是要重新创建activity,问题等于没解决。

所以综合一些原因,官方建议使用一个无界面的Fragment来保存Activity的状态数据。因为当 Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。 这里需要强调为了保留Fragment不被清理需要调用setRetainInstance(true)方法。关于这个方法的官方注释:

Control whether a fragment instance is retained across Activity re-creation(such as from a configuration change).
控制是否在重新创建Activity时保留Fragment(例如当Activity经历了系统配置变化后的重建)。

这里我也写了一个Demo跟大家共同学习下利用Fragment保留Activity信息的方法,首先默认不做任何恢复操作时的效果:

默认无恢复操作

可以看到界面上的进度信息随着转换屏幕重建Activity而中断。接下来看下加入恢复操作后的模拟效果:

加入Fragment进行恢复操作

可以Activity并没有因为旋转屏幕而丢失信息。这里贴一下我的实现方法,帮助大家抛砖引玉了:

Activity部分:

public class RotateActivity extends AppCompatActivity {
    private static final String TAG = "RotateActivity";
    private static final int ACTION_START = 0x1000;
    private static final int ACTION_END = 0x1001;
    private ProgressBar mPb;
    private TextView mTvShowProgress;
    private Button mBtnStart;
    private ProgressHandler mHandler = new ProgressHandler(this);
    private int progress;
    private Thread mThread;
    private volatile boolean exit = false;

    private BlankFragment dataFragment;
    private ViewBean viewData;

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

        Log.i(TAG, "onCreate()");

        mPb = findViewById(R.id.pb_rotate_ac);
        mTvShowProgress = findViewById(R.id.tv_rotate_ac);
        mBtnStart = findViewById(R.id.btn_rotate_ac);

        mThread = new SafeThread();

        mBtnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mThread.start();
            }
        });

        FragmentManager fragmentManager = getFragmentManager();
        dataFragment = (BlankFragment) fragmentManager.findFragmentByTag("data");

        if (dataFragment == null) {
            dataFragment = new BlankFragment();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.add(dataFragment, "data").commit();
        }

        if (dataFragment.getViewData() != null) {
            // restore view info
            viewData = dataFragment.getViewData();
            progress = viewData.getProgress();
            mPb.setProgress(viewData.getProgress());
            mTvShowProgress.setText(viewData.getProgress() + "%");

            mThread.start();
        }



    }

    private ViewBean collectViewData() {
        viewData = new ViewBean();
        viewData.setProgress(progress);
        return viewData;
    }



    @Override
    protected void onRestart() {
        super.onRestart();
        Log.i(TAG, "onRestart()");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i(TAG, "onStart()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i(TAG, "onResume()");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.i(TAG, "onPause()");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mThread.isAlive()) {
            Log.i(TAG, "close thread");
            exit = true;
        }

        Log.i(TAG, "onStop()");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy()");
        // store the data in fragment
        dataFragment.setViewData(collectViewData());
    }

    private static class ProgressHandler extends Handler {
        private WeakReference<RotateActivity> mContext;

        public ProgressHandler(RotateActivity context) {
            mContext = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case ACTION_START:
                    mContext.get().mPb.setProgress(msg.arg1);
                    mContext.get().mTvShowProgress.setText(msg.arg1 + "%");
                    break;
                case ACTION_END:
                    Toast.makeText(mContext.get(), "Download completed.", Toast.LENGTH_SHORT).show();
                    break;
            }

        }
    }

    private class SafeThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!exit && progress < 100) {
                progress++;
                Message message = mHandler.obtainMessage();
                message.what = ACTION_START;
                message.arg1 = progress;
                message.sendToTarget();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (!exit) {
                Message message = mHandler.obtainMessage();
                message.what = ACTION_END;
                message.sendToTarget();
            }
        }
    }

}

Fragment部分:

public class BlankFragment extends Fragment {
    private ViewBean mViewBean;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setViewData(ViewBean data) {
        this.mViewBean = data;
    }

    public ViewBean getViewData() {
        return this.mViewBean;
    }
}

需要简单说明下,这里的ViewBean类就是针对具体需求对需要保存的Activity信息进行的封装。我这里只简单的保留了进度信息,大家可以自身要求做具体的变化。

最后的总结,这篇文章的整体思路是基于《Android开发艺术探索》这本书的内容。此外,也增加了一部分自己整理的一些知识点,算是对这一阶段的学习的汇总。写完以后发现一个技术点如果真的整理起来可以扩展很多,原本以为自己已经掌握的点其实并没有完全理解透彻,想写一篇让自己满意的文章同时也需要大量的时间来探究和整理。也希望自己能够做到合理有效的利用时间来坚持做好这件事。期待与大家一同进步~

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

推荐阅读更多精彩内容

  • 启动与销毁Activity 不同于使用 main() 方法启动应用的其他编程范例,Android 系统会通过调用对...
    安卓Boy阅读 1,749评论 3 5
  • 启动与销毁Activity 不同于使用 main() 方法启动应用的其他编程范例,Android 系统会通过调用对...
    mouekz阅读 641评论 0 0
  • 您的应用中的Activity应该做到如下需求: 1.用户在使用应用时接听来电或切换到另一个应用,它不会崩溃。 2....
    正阳Android阅读 522评论 1 0
  • 在现在以及以后,我都会把知识点进行整理,为什么呢?虽然不整理也可以,用的时候去百度,百度?呵呵了吧大家,太菜了吧,...
    gehangAndWeb阅读 483评论 0 0
  • 这是我第三遍看《Android开发艺术探索》这本书了,从第一遍看的云里雾里,第二遍略微明白之后,我决定看第三遍,并...
    陈添阅读 850评论 2 8