前言

今天来梳理一下有关Service的问题。


Service是否在main thread中执行,里面是否能执行耗时的操作?

默认情况,Service和Activity是运行在当前app所在进程的main thread(UI 主线程)里面的。service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件)。如果需要作耗时操作需要在service里另外开启线程。也可以在清单文件配置service执行所在的进程,让service在另外的进程中执行。此外还可以使用IntentService来实现多线程的耗时任务处理。


启动Service的方法和其生命周期

Service的生命周期:
20160523175053216.png

可以看出Service的生命周期分为两种形式,第一种为非绑定模式,另一种为绑定模式。

  • 非绑定模式:
    当第一次调用 startService 的时候执行的方法依次为 onCreate()、onStartCommand(),当 Service 关闭的时候调用 onDestory 方法。此方法适用于仅仅开启一个后台服务的情况。
  • 绑定模式:
    第一次 bindService()的时候,执行的方法为 onCreate()、 onBind()解除绑定的时候会执行 onUnbind()、onDestory()。此方法应用于Service与Activity需要进行数据交互的时候。

两种方法的区别是:如果仅使用一个模式时,绑定模式的生命周期随着开启它的组件,比如Activity被销毁后,该Service就会被销毁。而非绑定模式与开启它的组件的生命周期无关,它仅于调用它的进程相关,该进程被杀死后,Service才会被销毁。

注:Service 实例只会有一个,也就是说如果当前要启动的 Service 已经存 在了那么就不会再次创建该 Service 当然也不会调用 onCreate()方法。
一个 Service 可以被多个客户进行绑定,只有所有的绑定对象都执行了onBind()方法后该Service才会销毁,不过如果有一个客户执行了 onStart() 方法,那么这个时候如果所有的 bind 客户都执行了unBind()该Service也不会销毁。


IntentService

特点:

  • 会创建独立的 worker 线程来处理所有的 Intent 请求;
  • 会创建独立的 worker 线程来处理 onHandleIntent()方法实现的代码,无需处理多线程问题;
  • 所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法停止Service;

值得一提的是,这里提到的worker线程其实是一个HandlerThread。在IntentService的方法中有一个叫onHandleIntent()方法,该方法就是运行在HandlerThread里的。我们可以在这方法里面做一些耗时的操作。具体可参考:IntentService


如何防止Service被系统自动杀死

具体方法可以参考:防止Service被系统自动杀死
其实上述方案只能做到降低Service被自动杀死的风险,而不能完全实现不被杀死这一流氓需求。

前言

intent在开发当中用得很多,有时候可能也会被封装好的工具类弄得并不能很好的理解intent的用法,今天来具体讲讲intent的用法。


显式intent

这里有一个关于intent七大属性的详细讲解,可以去看一下:关于Intent的七大属性
接下来就说一下显示的intent,这个其实很好理解。大体意思就是我有明确的目标去实现,就比如activity的跳转。

Intent intent=new Intent();
intent.setClassName(this,TextActivity.class);
startActivity(intent);

上述代码就是一个很简单的显式意图了,有明确的目标(TextActivity)跳转。还有一种写法是利用ComponentName来设置:

Intent intent=new Intent();
intent.setComponent(new ComponentName(this,TextActivity.class));
startActivity(intent);

当然我们还可以通过Extra来设置跳转时的数据传递。关于Extra后面会有讲述。


隐式intent

关于隐式调用设计到intent的Action,Category,Data,Type这几个属性,这里重点讲一下action和data,其他在上述的链接当中也有讲解。

  • Action——动作

    Intent intent=new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    startActivity(intent);

如上,我们在intent中设置一个action为Intent.ACTION_VIEW,代表浏览的意思。当程序执行到这里时,系统就会弹出所有满足这个action的应用让我们选择。TIM图片20180803165019.jpg

  • Data——数据条件(URI和数据类型)

我们还可以通过添加data实现数据的传递:

intent.setData(Uri.parse("http://www.baidu.com/"));

我们给data设置了一个url:http://www.baidu.com/,它的scheme为http,host为www.baidu.com,运行的时候首先会判断协议头http,然后通过http定义为我们需要打开一个浏览器去浏览www.baidu.com。如果您的手机有多个浏览器应用或者是登记了有该scheme为http的应用就会像上述一样通过弹窗提示选择应用打开了。

上面说了一种登记data的方法其实就是在项目的Androidmanifest.xml中预先设置好action和data,当运行上述代码时,就会在弹窗中多出自己的应用供用户选择。

<activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:scheme="http" android:host="www.baidu.com"/>
        </intent-filter>
</activity>

注:category节点是必须设置的,至少像上述那样设置一个默认的。
这个scheme还有很多,譬如tel、sms,电话协议与短信协议。逻辑都是类似的。
当然我们还可以通过自定义一些协议头来作一些隐式的调起操作,这个可能要视具体情况来设计了。


Intent传递数据时,可以传递哪些类型数据?

这是我上次去面试做笔试时遇到的一道题,这个问题还挺常见的。
intent可以通过putExtra来设置需要传递的数据,还有一种方法就是上述的setData方法。

  • Extra:我们可以通过putExtra方法来将想要传递的数据设置到intent当中。
    这个方法支持java的基本数据类型、String类型、实现CharSequence接口的类型及数组(如:String, StringBuilder和StringBuffer)、实现了Serializable的对象及其数组、实现了Parcelable的对象及其数组、Bundle类型对象

有关Serializable接口可以参考此文章Serializable

有关Parcelable接口可以参考此文章Parcelable

  • Data:根据上述所属,data是通过url的形式传输的,一般传输http、ftp协议的网络地址,或者根据某种协议传递字符串的数据。

Intent 和 Intent Filter

  • Intent:Activity,Service和broadcast receiver之间相互激活的手段。它的作用分为显式意图和隐式意图,就是上述所讲的内容。
  • Intent Filter:意图过滤,一般出现在android Manifest文件中,就像上面所述。有一种特殊情况就是在动态注册广播监听的时候会出现在代码当中:

    IntentFilter intentFilter=new IntentFilter();
    intentFilter.addAction("aaaa");
    registerReceiver(receiver,intentFilter);

上述的addAction方法对应就是android Manifest中的action结点了,当需要响应对应的广播时在intent中添加"aaaa"的action就会在对应的广播监听中接收到。

前言

今天看一些面试题总结了一些有关Activity的知识。


什么是Activity

Activity是Android四大组件之一,setContentView方法可设置该页面显示的布局。
Activity是Context的子类,同时也实现了window.callback、keyevent.callback的方法,实现处理与窗体用户的交互事件。
通常在项目中会自定义一个BaseActivity来规定一些Activity的共同特点与功能


有关Activity的生命周期

Activity的生命周期为onCreate->onStart->onResume->onPause->onStop->onDestory。分别对应从创建到销毁的不同过程。
其中生命周期的方法是两两对应的:

  • onCreate创建与onDestory销毁
  • onStart可见与onStop不可见
  • onResume可编辑(获取焦点)与onPause

除此之外还有onRestart方法,这个方法在Activity调用onStop之后,但没有调用onDestory时,再次启动该Activity时就会先调用onRestart->onStart->onResume。注意:再次启动时不会再调用onCreate方法,只有调用onDestory方法后才会再次调用onCreate。

通常在项目中,如果需要在Activity每次被启动时(包括调用onRestart方法返回)都需要重新加载新数据,可以将刷新数据的方法在onStart方法调用。

两个Activity之间跳转时,由A跳转到B,首先A会响应onPause方法,然后B依次响应onCreate、onStart、onResume,如果B覆盖了窗体,则A会响应onStop方法。如果B是透明或者是类似对话框的样式则A不会响应onStop方法。
注:可将Activity的主题设置成android:theme="@android:style/Theme.Dialog"使其变成一个窗口样式。


有关Activity横竖屏切换问题

  • 默认情况下,Activity进行横竖屏切换的时候会重新调用各个生命周期,默认是先销毁当前的activity重新创建。
  • 如果在Anroidmainfest.xml中对对应的activity设置android:configChanges="orientation|keyboardHidden|screenSize"在切换时将不会重新调用各个生命周期,只会执行onConfigurationChanged方法。

有关后台Activity被系统回收的问题

一般来说除了在栈顶的activity,其他activity都有可能在内存不足的时候被系统回收,也就是说一个activity越处于栈底,其被回收的概率就越大。
这时我们会使用Activity中的onSaveInstanceState(Bundle outState)方法来进行数据存储,将数据存储到Bundle类型的对象当中。
在onCreate(Bundle saveInstanceState)时可以先判断该Bundle对象是否为null,若不是就可以从中取出之前保存的数据。
onSaveInstanceState在Activity的生命周期为onPause -> onSaveInstanceState -> onStop


通过intent的flag安全关闭一个已打开多个activity的application

关闭一个已打开多个activity的application有很多,比如在application中专门设置一个存放每个activity对象的集合,遍历关闭;使用广播对每个在栈的activity传递关闭消息;或者使用startActivityForResult递归关闭等。
其实还有一个方法比较简单,就是给intent添加一个flag:Intent.FLAG_ACTIVITY_CLEAR_TOP,
激活一个新的activity,然后在activity中finish。
具体做法可参考:Android 关闭多个视图Intent.FLAG_ACTIVITY_CLEAR_TOP用法


同一个程序中,不同的activity存放在不同的栈中

  • 可以在Androidmainfest.xml中设置对应activity为Signalinstance,该标志可以使设置后的activity在启动时默认在另外一个单独的任务栈中创建。
  • 在activity跳转时,可以给intent添加flag:Intent.FLAG_ACTIVITY_NEW_TASK,使其可以在另外一个单独的任务栈中创建。

前言

之前在讲WebView缓存时有讲过一种DomStorage缓存方式。这篇文章就详细讲一下这个缓存机制,虽然这个跟web端的关系大一些,也就当作一次知识拓展吧!


webStorage

在html5中DomStorage称为webStorage。
其相关api有:

    setItem (key, value) ——  保存数据,以键值对的方式储存信息。

    getItem (key) ——  获取数据,将键值传入,即可获取到对应的value值。

    removeItem (key) ——  删除单个数据,根据键值移除对应的信息。

    clear () ——  删除所有的数据

    key (index) —— 获取某个索引的key

其类型可分为:sessionStorage和localStorage两种。


localStorage

localStorage的生命周期是永久性的。假若使用localStorage存储数据,即使关闭浏览器,也不会让数据消失,除非主动的去删除数据,使用的方法如上所示。localStorage有length属性,可以查看其有多少条记录的数据。使用的方法也很简单:

  • storage = window.localStorage; ——先获取localStorage的对象
  • storage.setItem("name", "Rick"); ——调用setItem方法,使用key-value的形式存储数据
  • storage.getItem("name"); ——调用getItem方法,获取对应key值的数据
  • storage.removeItem("name"); ——调用removeItem方法,移除对应key值的数据

以上就是localStorage的基本用法了。注意!以上的是js代码


sessionStorage

sessionStorage 的生命周期是在浏览器关闭前。也就是说,在整个浏览器未关闭前,其数据一直都是存在的。sessionStorage也有length属性,其基本的判断和使用方法和localStorage的使用是一致的。
主要的特点是:

  • 页面刷新不会消除数据
  • 只有在当前页面打开的链接,才可以访sessionStorage的数据
  • 使用window.open打开页面和改变localtion.href方式都可以获取到sessionStorage内部的数据

以上就是webStorage的基本介绍了,以下链接有比较详细的介绍:LocalStorage和sessionStorage之间的区别


Environment.getExternalStorageDirectory()究竟是哪里,以及它与getExternalFilesDir()的区别

上次讲WebView缓存的时候有用到Environment.getExternalStorageDirectory()来获取应用的外部存储路径。这里插一个题外话,其实不完全是WebView,所有关于文件读写的操作都需要获取到应用的内存地址。这个在做相机拍照、录像的时候也时常碰到的问题,就是到底Environment.getExternalStorageDirectory()是在哪里呢?
ExternalStorageDirectory:故名思义就是外部存储。说到外部存储我们的第一反应就是外置的SD卡。其实我刚开始做开发的时候也有过一些疑惑,因为现在大部分手机都不存在SD卡这个概念了,那会不会导致这个方法出现异常呢?其实是不会的。这里的外部存储是相对于手机本身的存储空间而言的。什么意思呢?其实大概就分为两种情况:

  • 第一种就是以前旧版本的手机可以插外置SD卡的,这一类手机的ExternalStorageDirectory一般会认为是这张SD卡的区域。
  • 第二种就是我们现在比较常见的无外置SD卡内置大容量的手机了。这部分的手机硬件一般会将手机容量分出一部分空间用于安装手机系统,而剩余的一般就是所谓的ExternalStorageDirectory(大概就是它们在这个内置内存中划分了很大一部分区间作为所谓的外部存储空间)。所以说即使一台256G的手机真正可以给用户自由分配的空间也必然会比256G少。

综合以上两种情况可以得知其实ExternalStorageDirectory只是一个相对的概念,而不是我们经常所说的外置SD卡。以下是Google官方文档的解释:
don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer
其意思就是这个目录可以更好地被认为是媒体/共享存储。它是一个文件系统,可以保存相对大量的数据,并在所有应用程序之间共享(不强制执行权限)。传统上,这是一个SD卡,但它也可以被实现为与被保护的内部存储区不同的设备中的内置存储,并且可以作为文件系统安装在计算机上。
简单一点理解就是其实Android手机也可以作为U盘来存储数据嘛。所以它本身其实也可以看作是一张SD卡。


getExternalStorageDirectory()与getExternalFilesDir()

  • 上面说到了ExternalStorageDirectory是一个在所有应用之间共享的文件系统,那么他的路径就必然是相对于其他应用独立的。也就是说在其路径下保存的文件可以对其他应用共享。

我们可以获取ExternalStorageDirectory的路径

File sdCard = Environment.getExternalStorageDirectory();
Log.i(TAG,sdCard);

打印出来的路径为

/mnt/sdcard
  • 再来说说getExternalFilesDir()。之前说过ExternalStorageDirectory是相对应用独立的文件路径,有独立就会私用的概念,也就是应用专属的文件。其路径一般会在应用的包目录下:

    /Android/data/< package name >/files/

我们可以通过getExternalFilesDir(null)查找到上面提到对应应用的files目录。
如果需要查找files下对应的目录,可以将所查找的目录名作为字符串传入方法中:
如我需要查找files下的Cache目录,getExternalFilesDir("Cache")