LeoYan Blog

技术分享,生活记录。

0%

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

ContentProvider 是什么?

  ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法。

  • ContentProvider 可以指定需要共享的数据,而其他应用程序则可以在不知道数据来源、路径的情况下,对共享数据进行增删改查等操作。

  • 在 Android 系统中,许多 Android 系统内置的数据也是通过 ContentProvider 提供给用户使用,例如通讯录、音视频文件和图像文件等。

URI

  URI(统一资源标识符)代表要操作的数据,可以用来标识每个 ContentProvider,这样你就可以通过指定的URI找到想要的 ContentProvider, 从中获取或修改数据。

  在 Android 中 URI 的格式如下所示:

1
content://com.leo.peopleprovider/people/7

  它可以分为如下三部分:

  • content://

  这个部分是 Android 的 ContentProvider 规定的,就像是上网的协议默认是 http:// 一样。暴露 ContentProvider、访问 ContentProvider 的协议默认是 content:// 。

  • com.leo.peopleprovider

  这个部分就是 ContentProvider 的 authorities (主机名)。是唯一标识符,用来定位 ContentProvider。系统就是由这个部分来找到操作哪个 ContentProvider 的。

  • /people

  资源部分(或者说数据部分)。指向一个对象集合,一般用表的名字。当访问者需要访问不同资源时,这个部分是动态改变的。

  • /7

  指向特定的记录,这里表示操作 people 表 id 为 7 的记录。如果要操作 people 表中 id 为 7 的记录的 name 字段,这部分应为 /7/name 即可。

其中 /people 部分和 /7 部分:是每个 ContentProvider 内部的路径部分

URI 模式匹配通配符

*  匹配的任意长度的任何有效字符的字符串。

# 匹配的任意长度的数字字符的字符串。

如:

  content://com.leo.peopleprovider/*   匹配 provider 的任何内容 url

  content://com.leo.peopleprovider/people/#   匹配 people 表中的所有行

  

MIME

  MIME,全称 Multipurpose Internet Mail Extensions,多功能 Internet 邮件扩充服务。MIME 类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。Android 中的工作方式也是类似的,在 ContentProvider 的 getType(Uri) 方法中,可以显示的返回一个 MIME 类型,该方法返回一个字符串,可以是任意的字符串,当我们显示的返回该 MIME 类型的时候,相当于通过该方法的验证,Provider 可以识别自身其他方法返回的 Cursor 的内容,不需要在进行更多的验证;如果返回其他的字符串 (非 android 能够识别的 MIME 类型,例如直接返回当前的包名),则 Provider 在执行其他方法后,返回 Cursor 类型的时候,需要再次进行验证。MIME 类型一般包含两部分,如:   

text/html

text/css

text/xml

application/pdf

  分为类型和子类型,Android 遵循类似的约定来定义 MIME 类型,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。

  集合记录:

1
vnd.android.cursor.dir/自定义

  单条记录:

1
vnd.android.cursor.item/自定义

  vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android 中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。

  在使用 Intent 时,会用到 MIME,根据 Mimetype 打开符合条件的活动。

  下面分别介绍 Android 系统提供了两个用于操作 Uri 的工具类:ContentUris 和 UriMatcher。

ContentUris

  ContetnUris 包含一个便利的函数 withAppendedId() 来向 URI 追加一个 id。

1
2
3
4
Uri uri = Uri.parse("content://com.leo.peopleprovider/people")
Uri resultUri = ContentUris.withAppendedId(uri, 7);

// 生成后的Uri为:content://com.leo.peopleprovider/people/7

  同时提供 parseId(uri) 方法用于从 URL 中获取 ID:

1
2
3
Uri uri = Uri.parse("content://com.leo.peopleprovider/people/7")
long personid = ContentUris.parseId(uri);
// 获取的结果为:7

UriMatcher

  UriMatcher 本质上是一个文本过滤器,用在 ContentProvider 中帮助我们过滤,分辨出查询者想要查询哪个数据表。

  举例说明:

  • 第一步,初始化:
1
2
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
  • 第二步,注册需要的Uri:
1
2
3
4
// MULTIPLE_PEOPLE 和 SINGLE_PEOPLE 是两个 int 型数据
matcher.addURI("com.leo.peopleprovider", "people", MULTIPLE_PEOPLE);
matcher.addURI("com.leo.peopleprovider", "people/#", SINGLE_PEOPLE);
// 如果 match() 方法匹配 content://com.leo.peopleprovider/people 路径,返回匹配码为 MULTIPLE_PEOPLE
  • 第三部,与已经注册的Uri进行匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 
* 如果操作集合,则必须以 vnd.android.cursor.dir 开头
* 如果操作非集合,则必须以 vnd.android.cursor.item 开头
*/
@Override
public String getType(Uri uri) {
Uri uri = Uri.parse("content://" + "com.leo.peopleprovider" + "/people");
switch(matcher.match(uri)){
case MULTIPLE_PEOPLE:
return "vnd.android.cursor.dir/people";
case SINGLE_PEOPLE:
return "vnd.android.cursor.item/people";
}
}

ContentProvider 的主要方法

public boolean onCreate()

  ContentProvider 创建后或打开系统后其它应用第一次访问该 ContentProvider 时调用。

public Uri insert(Uri uri, ContentValues values)

  外部应用向 ContentProvider 中添加数据。

public int delete(Uri uri, String selection, String[] selectionArgs)

  外部应用从 ContentProvider 删除数据。

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):

  外部应用更新 ContentProvider 中的数据。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 

  供外部应用从 ContentProvider 中获取数据。  

public String getType(Uri uri)

  该方法用于返回当前 Url 所代表数据的 MIME 类型。

ContentResolver

  ContentResolver 通过 URI 来查询 ContentProvider 中提供的数据。除了 URI 以外,还必须知道需要获取的数据段的名称,以及此数据段的数据类型。如果你需要获取一个特定的记录,你就必须知道当前记录的 ID,也就是 URI 中 /7 那部分。

  ContentResolver 类提供了与 ContentProvider 类相同签名的四个方法:

public Uri insert(Uri uri, ContentValues values) // 添加

public int delete(Uri uri, String selection, String[] selectionArgs) // 删除

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) // 更新

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) // 获取

  实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ContentResolver resolver =  getContentResolver();
Uri uri = Uri.parse("content://com.leo.peopleprovider/people");

// 添加一条记录
ContentValues values = new ContentValues();
values.put("name", "fanrunqi");
values.put("age", 24);
resolver.insert(uri, values);

// 获取 user 表中所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
while(cursor.moveToNext()){
// 操作
}

// 把 id 为 1 的记录的 name 字段值更改新为 finch
ContentValues updateValues = new ContentValues();
updateValues.put("name", "finch");
Uri updateIdUri = ContentUris.withAppendedId(uri, 1);
resolver.update(updateIdUri, updateValues, null, null);

// 删除 id 为 2 的记录
Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);
resolver.delete(deleteIdUri, null, null);

ContentObserver

  ContentObserver (内容观察者),目的是观察特定 Uri 引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当 ContentObserver 所观察的 Uri 发生变化时,便会触发它.

  下面是使用内容观察者监听短信的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MainActivity extends Activity {

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

// 注册观察者Observser
this.getContentResolver().registerContentObserver(Uri.parse("content://sms"),true,new SMSObserver(new Handler()));

}

private final class SMSObserver extends ContentObserver {

public SMSObserver(Handler handler) {
super(handler);
}

@Override
public void onChange(boolean selfChange) {

Cursor cursor = MainActivity.this.getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);

while (cursor.moveToNext()) {
StringBuilder sb = new StringBuilder();

sb.append("address=").append(
cursor.getString(cursor.getColumnIndex("address")));

sb.append(";subject=").append(
cursor.getString(cursor.getColumnIndex("subject")));

sb.append(";body=").append(
cursor.getString(cursor.getColumnIndex("body")));

sb.append(";time=").append(
cursor.getLong(cursor.getColumnIndex("date")));

System.out.println("--------has Receivered SMS::" + sb.toString());
}
}
}
}

  同时可以在 ContentProvider 发生数据变化时调用
getContentResolver().notifyChange(uri, null) 来通知注册在此 URI 上的访问者。

1
2
3
4
5
6
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
}
}

 

 

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

sy_40_1

修图App

关于文中所提及的APP下载地址(豌豆荚)

  VSCO:http://www.wandoujia.com/apps/com.vsco.cam

  Snapseed:http://www.wandoujia.com/apps/com.niksoftware.snapseed

  Layout:http://www.wandoujia.com/apps/com.instagram.layout

  图片合成器:http://www.wandoujia.com/apps/cn.poco.pMix

相关文章

关于手机摄影的几点建议(一)从零开始

关于手机摄影的几点建议(二)如何构图

关于手机摄影的几点建议(三)光与弱光

前言

  这次后期篇我介绍四款后期 APP,即第一期所建议的四个 APP,我会从一张张图的示范给给你们讲一下我对于后期修图的理解,这次的图片示例来自不同的设备,大部分为T1,我会一一注明,话不多说,进入正题。

VSCO

  第一款介绍的是 VSCO,这款基本可以说是修图必备的人气 APP 了,软件的详细功能之类我不会讲,毕竟我不是在做 APP 推荐帖子;我们直接来看第一张图。

sy_41

  这种图我的第一反应是背景变白,那么就需要加曝光。基本思路就是加曝光,调一下对比度和色温让画面平衡一下,再弄个自己中意的颜色。

sy_42

  我们打开 VSCO,进入修图界面,首先选择一个滤镜(看个人喜好和对色彩的感觉),再点一次选中的滤镜,进行滤镜程度的调整;这里我选的是 A1、+10。

sy_43

  接下来按照软件的顺序,进行第二项调整,微调。首先是对比度和曝光的调整,两者协调一下,这张图我的目的是使背景变白,同时保持主体色彩不过度修。

sy_44

  然后是按照软件的顺序进行裁剪和色温的调整,这张图我们自然是裁剪为完完整整的正方形,同时让碗成为中心的一个圆,适当调整色温(个人喜好,取决于照片内容,调得偏冷 / 暖);虽然我是按照软件排列顺序做的,但是我建议,首先进行裁剪,再对色彩、曝光、对比度等进行调整。

sy_45

  最后,因为个人觉得颜色不够艳,加一点饱和度,旋转是为了让筷子保持水平。

sy_46

  最后完成,保存图片,我们来看看最后的效果图吧。

sy_47

  接下来我们来看第二张图。

sy_48

  很明显,主体是人和窗户那里,其他地方呢?得全部去掉;另一方面图片的色彩并不好看,那怎么办?转为黑白。

sy_49

  同样的,首先选择色彩,这里我们需要选择B系列(黑白滤镜)。

sy_50

  然后进行裁剪,裁剪这张图我多讲一点,也就是关于后期的二次构图。基本点我已经用红线画出来了,可以看到,这里地面的位置我选择了三分线的位置,人物没有完全的位于黄金分割点,这里为了让上面部分对称且去掉右边红圈圈出来的杂物,这就是需要取舍的地方。

sy_51

  裁剪过后,调节曝光和对比度到一个合适的程度,注意这张图本来光线不好,过曝会使得画面出现大量噪点;另一方面我发现噪点基本是出现于边角,于是我们可以选择加暗角,使得边角的噪点消失。最后保存图片,同样的,我们来看下修改后图片。

sy_52

  下一张图,我讲一下夜景的修图。

sy_53

  夜景我想主要是噪点和色彩这两方面,处理噪点,同样的我们需要降低曝光值,色彩方面,调节对比度、色温、饱和度即可。

sy_54

  选择滤镜,降低曝光。

sy_55

  加对比度,裁剪,相信大家对这些已经比较熟悉了。这张图的色彩我认为已经够艳了,所以并没有调节饱和度;最后我们看一下修改后的图片。

sy_56

  最后介绍一个玩法,大家可能会比较好奇我拍的坚果手机的多彩背景(下图),接下来我就叫你如何用 VSCO 快速完成这种效果。

sy_57

sy_58

  其实是十分简单了,真正的一键完成。

sy_59

  首先我们进入修图界面,选择最后一个图标,带 H 字母的那个,然后进入选择你喜欢的色彩即可。

sy_60

  对于图示这种效果,自然是需要图片背景是白色,主体却几乎没有白色或者浅色这种照片才可以的,大家可以看到,它其实是调节高光色调的。

  最后说明几点:

  • 1.修图这种东西,个人认为还是多看,去优秀的作品里培养对色彩的感觉,然后自己去慢慢形成一个自己的风格。

  • 2.针对于每张图片,所微调的地方肯定不一样,所以建议不要去照搬什么调色教程里的参数,看那样的教程对于学习软件是可以的,照搬肯定不可行。

  • 3.VSCO 这款软件的大部分滤镜需要购买,希望大家支持正版;不过我暂时未找到安卓版本方便的购买方法(现在似乎需要去 Google Play 购买,欢迎纠正),我自己是在 IOS 端购买的,然后账号同步到安卓版上面的。

  • 4.这款软件的更多功能,有兴趣的朋友可以去尝试一下,例如相机和社区,还有修图方面的阴影、高光调节等等,大家多多尝试,自然知道它们是干什么的了。

Snapseed

  这款软件相比 VSCO 更加专业,我主要使用它的几个功能,这次也主要是推荐这几个功能给大家。同样的,从图片说起。这张图片熟悉吧?没错,就是我们刚才用 VSCO 调整完的图片,那么它还有什么问题呢?

sy_61

  仔细观察,因为镜头的缘故,画面边缘发生了变形,本应该直立的墙,却变斜了。这时候我们需要用到 Snapseed 的变形功能。

sy_62

  通过相应的调整后(这个具体操作大家去尝试就明白了),我这里给出最终图。

sy_63

  这里很不要脸的道歉一句:调整后的图片大家可以看到,墙是好了,但是水平线有点问题,这个需要在 Snapseed 里面再仔细的调整一下,时间缘故,我不再去调整了。请各位带着对我的宽恕,我们进入下一张。

sy_64

  这张图本身也是 VSCO 调整过色彩的图,我们可以看到,问题在于垃圾桶,图片摄于八达岭长城,这自然不能有垃圾桶啊;于是,我们的 Snapseed 的局部调整和修复就派上用场了。

sy_65

  选择局部,点左下角的加号,再在图片上面点一下,会出现一个蓝色圆圈,它可以移动,可以更改调整的内容,可以修改调整的范围,我们把它移动到垃圾桶上面,并把范围(出现红光区域)缩小,然后滑动调整(这个需要你们去用用 Snapseed ),使得局部变暗。

sy_66

  同样的手法,加入很多个蓝色圆圈进行调整,然后点完成,再进入( Snapseed 是覆盖性的调整,这点不同于 VSCO ),再进行调整,直到足够黑为止;调到这里,我们仍然会看到有部分垃圾桶的影子,这时候我们就需要 Snapseed 的修复功能。

sy_67

  用修复功能,先把图片放大,再去小心的涂抹(它的原理是将你选择的地方抹去,用周围的图片来填充,这可以运用于去污点),直到慢慢的把所有的痕迹都涂抹掉。

sy_68

  最后,我们进入 Snapseed 的图片调整,进行暖色调和饱和度调整(VSCO 也能调整,但是程度没有 Snapseed 这么大,注意一下,我上图是先调整的右边,再调整的左边,对于这个错误,十分抱歉)。最后我们看看对比图。

sy_69

sy_70

  最后介绍一个功能:细节;简单说一下就好,这次没有原图也没有对比图(不要打我)。

sy_71

sy_72

  细节调整是用于上面这种类似图片,需要表现细节的时候可以尝试一下,在不毁掉图片的情况上,提升细节的观感。

  最后说明几点:

  • 1.Snapseed 提供了很多功能,它的自带滤镜我点过几次,个人认为惨不忍睹,所以也不建议你们去使用。

  • 2.我这次介绍的几个功能是我认为比较不错的,希望大家去尝试一下。

  • 3.出于被谷歌收购的原因,这款优秀的软件完全免费。

Layout

  怎么描述这款软件呢?简单好用的拼图软件。大家可以利用它,多多创造好玩的照片哦;我这里给两个示范,希望可以给大家一点灵感。

sy_73

  进入软件,操作非常简单,选择你想要拼的图片,再选择你想要的布局,然后自动进入下一步。

sy_74

  对图片进行翻转和镜像的调整,做出你想要的感觉。

sy_75

  一张照片可以拼,多张自然也可以的。

sy_76

  调整好图片位置,然后保存,分享;还是给各位看看最后的效果图吧。

sy_77

sy_78

  这款软件同样也是免费的,并且十分的简单易用。

图片合成器

  它来自国内,我这样说是要告诉你,虽然它可以实现双重曝光这个效果,但是界面花哨、插件多、广告也有,体验并不是很好,不过好在我们若是不管其他的,只用基础功能来做双重曝光,它这些就不影响我们。

sy_79

  首先进入软件,选择经典合成,然后选择照片。双重曝光最重要的还是对照片的选择,左边的照片需要选择图示这种,干净背景的照片;右边的照片,要求并不是很高,看个人喜好,可以是楼房、花海等等;选好后我们进入下一步。

sy_80

  在下方列表选择滤镜和效果,上面调整一下画面,然后就可以保存分享啦,操作还是很简单的;给大家看一看效果图。

sy_81

结语

  终于把这最后一篇完成了。如果大家觉得还可以,请点个喜欢或关注,多谢大家。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

sy_27_1

相关文章

关于手机摄影的几点建议(一)从零开始

关于手机摄影的几点建议(二)如何构图

关于手机摄影的几点建议(四)后期修图

前言

  这一期我主要讲两个事情,一是「多利用光线」,二是「弱光能拍什么」;下面我们直入正题。

天气好就该去拍照

  看到外面阳光明媚,特别是现在处于冬天,好不容易的好天气,我们自然是想出去玩啦,要是有仍然想待在家里的朋友,那为了拍更好的照片,你也应该出去走走呢。总之,别总待在家里。

  相信大家都很容易发现,天气好的情况下,我们用手机拍的照片,画质会好很多,这便是光给我们的好处,所以各位,一定不要放过天气好的日子。

sy_28

  这次的示例照片没有很多,主要是其实「用光」这一点,也是我的弱点。出于一开始养成了习惯,很随意的拍,没有太在意光这方面,我现在是后悔了,所以才来建议大家要重视这个。

  当然,天气好的时候,也有分适合拍照的时候,这便是人们常常讲的「摄影黄金时间」;摄影黄金时间是日出后的一小时与日落前的一小时左右,此时太阳位置较低,在太阳低角度的照射下,柔和光线使景物产生较长的阴影,使得画面更加精彩。

sy_29

  当然,这个黄金时间,基本也是我们正在睡觉/吃晚饭的时间,所以要拍好照片,也不是那么容易的。示例图片也离黄金时间差了一点,出于版权的问题我没有给大家上网找类似图片,关于对光线运用的照片示例,我建议大家去看看刘辰的作品,在第一期的网站推荐中,LOFTER里可以看到他的照片,他的主页地址是:http://coculiu.lofter.com/

sy_30

  这是题图的照片,老实讲,这是我为这期帖子前几天专程去照的。是的,我的习惯已经坏了点,对光线不敏感,最近我在尽力改善这点。我希望大家可别学我,一定要对光线敏感。

  如我前面所说,光线带给我们的,不仅仅是更好的画质,还有我们平时所未在意的景和物。

  最后对于光的建议是,如果不是照片需要,尽可能的顺光拍,而不是逆光拍。但是逆光拍可以产生剪影的效果,具体效果大家可以看接下来夜间的图。

光线差也别放弃照

  光线好的时候应该多照,那么光线差的时候呢?特别是夜间,大家想拍夜景,却又觉得我们的 Smartisan T1 夜间成像不够好,就放弃了。难道用手机就不能拍夜景了吗?当然不是的。接下来我将讲一些我对于手机摄影面对光线差以及夜间的看法。

sy_31

  非夜间光线差的情况我只讲这一张图。这是摄于上海当代艺术博物馆内,四周几乎无窗,大家可以看到这张图的画质是比较差的,而且我还几乎裁剪了一半。但是这张照片的内容很不错,情侣坐在一起看电影。所以我要说的是:1.摄影是为了记录,在内容好的情况下,我们就别太在意画质;2.在色彩适合转黑白的情况下,转黑白也许可以弥补画质的不足。关于后期的事情,下一期我会着重讲。

sy_32

  接下来开始说夜间照相。当然,第一步是把我们相机里的「夜间模式」打开,相信这个大家都会。需要注意的是,Smartisan OS的夜间模式是连续拍几张照片然后合成进行降噪处理,所以我们在拍照的时候,尽可能的保持手稳,直到照片拍完。上图即为用「夜间模式」拍的照片,这照片我另外想说的是,手机的夜景大家其实都清楚是怎么回事,所以不要太在意细节,我们把握整体的感觉,比如上图的整体颜色就让人看起感觉还不错。

sy_33

sy_34

  夜间还能拍什么?拍光,对着光拍。图一呢即是前文所提到的逆光拍产生剪影的效果,白天当然也可以逆光拍产生这种效果,只要运用于恰当的地方即可。对着光拍我们可以看到,光的部分可以完整的拍下来,至于其他部分呢,我们通过后期调色处理为纯黑色,做到一个留白(还是留黑呢?)的效果。

  还有一点,这里的光,我指的是夜间你所能看到的,灯光、火光等等。

sy_35

  有些时候对着光拍,但是周遭的色彩并不是很有特色甚至显得杂乱的时候,我们可以选择转为黑白色。一方面画质看起来更好了,另一方面画面也变得简单起来。

sy_36

sy_37

sy_38

  这三张图相信大家可以看出共同点,即失焦。

  它的优点我想是显而易见的,一是画质对它影响不大,二是好看。接下来我教大家如何用T1拍出这样的照片。

  【此处更新,发现Smartisan OS相机支持长按锁焦功能,所以拍失焦照片更加简单,感谢论坛朋友skyoss提醒】

  1.对近处进行长按对焦,也可以是对自己的左手长按对焦(如果你用右手拍照),锁焦成功后系统会有提示

  2.对准你想拍的地方拍照即可,然后进行后期处理

  3.进行此拍照时候可打开夜间模式

  希望大家都能拍出这种风格的照片。在有些场景下,我相信它除了好看之外,还是会有其他意义的,比如梦幻的一种感觉。

sy_39

  这张图是年初拍的了,大家看到的这台手机是老去的Nokia Lumia920,我给这张照片想说的是,在背景灯光漂亮的情况下,对近处的物/人进行拍照,也会有不错的效果哦,兴许可以用这样去拍女朋友哦,不过我没有尝试过,大家可以试看。

结语

  这一期的分享就到此结束了。

  至此,我简单的说了对于手机摄影的理解,从最基础的准备,到最重要的构图,以及今天进阶的用光。我的观点始终是内容第一,画质第二;马上这一系列建议也将迎来最后一篇了,即后期篇。但是请大家明白,需要后期的照片,一定是值得后期的照片,而这一切,需要靠前期的内容和构图。

  如果大家觉得还可以,请点个喜欢或关注,多谢大家。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

sy_14_1

相关文章

关于手机摄影的几点建议(一)从零开始

关于手机摄影的几点建议(三)光与弱光

关于手机摄影的几点建议(四)后期修图

前言

  上一期我主要讲了一些开始「手机摄影」需要养成的习惯,以及一些从无到有的学习建议,即准备、构图、后期、练习和多看。

  那么这一期主要是对构图展开来讲,可能没有你所期望的各种构图的介绍,我主要以一年来用 Smartisan T1 所摄的照片为例,讲一下我对构图的理解。

怎样构图

  到底要怎样构图?我总觉得这不该有一套完整的体系,它应该是随性的,是自由的;能让一张照片表达出该有的感情的,就是好的构图。

  网上有不少关于各类构图的介绍,诸如「三分法」、「黄金分割」、「对角线」等等,有兴趣的朋友可以去网上搜一下,大同小异的讲法,但是我觉得那些只看一看就好,甚至,看都别看。

  构图,是一种感觉;构图的功力,就像炒菜对火候把握的功力一样。

sy_15

  我们先看第一张图,正方形比例裁剪,月亮放在画面中间,右侧的树枝,左侧的留白,天的蓝色,这一切都恰到好处;所以我们构图是为了什么?为了把本来的美更好的展现出来。

  这里给出本篇贴的第一个建议:画面元素尽可能的少;可以看到,本图一共三种颜色,月亮、树枝、天空,这样就够了。

sy_16

  同样的,简单的元素,人物的位置大家可以看到,大概是位于「黄金分割点」的,个人认为,「黄金分割点」的目的是让画面主体看起来平衡。

  这张图并没有用正方形裁剪,但是结合一二两图我对「正方形构图」说一下,个人建议是:1.尝试去用,但不是每张图都适用;2.社交网络分享的时候(新浪微博、朋友圈等),多图分享的小图排版时候会自动展示正方形裁剪后的图片,既然如此,我建议是网络多图分享时,都裁剪为正方形,避免系统自动裁剪使得照片不好看。

sy_17

  说复杂点,这可能就是所谓的「三分法」,画面主体位于图片的三分之一处,但是我们平时生活所见,不也应该是这样子吗?所以,构图是件很自然的事情,源于对生活的观察。

  当然,画面的倒影、放风筝的人、风筝,这些元素,都是需要你的观察,以及你的构图,将其带入画面中。

注:这张照片是iPhone5S拍摄的,并非T1,因为当时两个手机都在身边,现在只用T1了,久了我给弄混了,十分抱歉

sy_18

sy_19

  这两张图放在一起,我想讲的是「留白」,第二张图片可能更能体现这个主体。请记住:留白是一种美。

  第二张图在构图上还有一点,首先主体有点类似四分之一圆,那么它处的位置,也基本上占据了画面的四分之一;你问这有什么意义呢?我答不上来,但是我总是感觉,这样子它更耐看一些,或许是「几何美」。

sy_20

sy_21

  看到这两张图,很明显的一个感受是什么?纵深。

  怎样拍出纵深感的照片呢?去发现、多尝试,构图则体现在尝试这里,我们发现一个有纵深感的地方,怎么拍?站着拍、蹲着拍、趴着怕、侧面拍、正面拍,在一个地方多次尝试,最后慢慢筛选,总有想要的。

  同样,对第二张图做一点补充;我朋友看见这张图的第一反应是,要是没有那个穿红衣服的人就好了,而我的反应是,红衣服的人物是点睛之笔。这可能是每个人的看法不同了,于我这里,我认为红衣人倚靠在远处,以他的孤独加强了图片的纵深感,或者说,图片的纵深感加强了他的孤独,这样有所意义的画面不是更好?

sy_22

sy_23

sy_24

  这三张图我们看到的又是什么呢?对称。

  对于对称,我能想到的还是观察,对生活周边的观察,对你所想照的景的观察到位了,比如在上面三张图的情况下,去尝试对称构图,是自然而然的事情。

sy_25

  再谈自然而然。

  这张图我认为自然而然的会把酒瓶放在构图的中央,并且采用正方形裁剪,我如此强调自然而然的原因是希望你们明白,构图是一种需要培养的感觉。

sy_26

  这张是典型的把主体(人)放在了「黄金分割点」上面,我其实用这样的构图特别多,因为实用,还是之前说的,让画面看起来平衡,这也是我为什么会给你们在第一篇帖子里介绍它的原因。

  这张图还有一个比较好的地方是,水与岸边碎石所成的分界线,一定程度上产生了「几何美」,这是我今天第二次提到几何美了,也希望大家平时可以尝试多关注一下这方面的东西,有时候的确是很美的。

结语

  很快很快,这一次的摄影建议也马上结束了,基本比较值得讲的照片我都说了一下我的想法(除了有些照片留在后面的建议「弱光下拍什么」来讲以外),希望大家都有所收获。

  如果大家觉得还可以,请点个喜欢或关注,多谢大家。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

sy_01_1

相关文章

关于手机摄影的几点建议(二)如何构图

关于手机摄影的几点建议(三)光与弱光

关于手机摄影的几点建议(四)后期修图

前言

  本文中用到的手机为 Smartisan T1,但目前大多数手机都会有类似的功能,所以不仅限于 Smartisan OS 的手机。

手机的自带相机

sy_02

  大家可以看到,这是我们 Smartisan OS 的相机界面,有一点不同的是,我这个界面有九宫格线,我的第一个建议便是大家首先应该养成一个习惯:把相机的九宫格线先打开。

  具体方法即点击相机上方的设置图标,再点击下图所示位置即可。

sy_03

  你可能会问,这个打开是为了什么?我最开始使用相机的时候也不喜欢这个线,总觉得挡着我的视线;但是,现在我离不开它了,因为它是我们拍好一张照片的第一步:为了更好的构图。有关构图的,我们留到后面再讲。

  第二个建议是,夜晚拍照的时候打开夜景模式,打开方式类似于打开九宫格,但是夜景模式的图标为「月亮」。

  至于其他几个功能,如全景模式、HDR模式,本文不打算提及,有兴趣的朋友可以自己去尝试一下。

  另外关于自带相机应用我想提醒大家的一个小功能是:两侧的按键,亮度键的控制曝光,音量键的则为快门按钮。

关于内建修图功能

  相信大家都知道我们的Smartisan OS的相册软件中内建一个修图功能,但是我想大多数人很少去使用它。对于它,我想说的是:简单、轻便、快捷。所以我的建议是,对于一些简单的修图,完全可以尝试用内建修图功能去完成。接下来我以一张图片为例来介绍一下这个内建修图。

sy_04

  如图点击箭头所指图标,进入内建修图功能。

  然后我们可以看到,里面总共有四个选项,分别是色彩、水平调整、裁剪、图画,这里我们主要介绍前三个,第四个功能大家可以看到,我演示所打的红色剪头,一部分就是第四个功能所完成的,但是它和我们今天所谈的摄影建议没有什么关系。

  首先我们选择所想要的颜色风格

sy_05

  之后是水平调整,这张图大家可能看不出什么区别,但是对于海边之类的照片,差别一看便知,后面我也会谈到这个

sy_06

  最后,裁剪大小,我的建议是多尝试正方形比例,以除去不必要的画面

sy_07

  最后点击完成(Done)

sy_08

  相信大家对于内建修图功能已经有一定的了解了,这里我给的建议是:单纯的水平调整、图片裁剪,可以选择用内建修图功能来完成。

推荐摄影APP

  对于更多的功能和风格需求,我们自然需要去寻找合适的第三方摄影APP。这里我推荐几个我常用的APP,后续的建议里我也讲一一进行详细介绍。

sy_09

  以下是由这四个软件编辑的照片

sy_10

sy_11

三个构图建议

  构图有太多种,我在这里只介绍三个,并不是说这三个最重要,只是在我个人平时的使用来看,这三个比较实用,且在大多数图片上面是不可丢失的构图要求。

sy_12

  虽然说三点,但是我们就在这一张图上面说。

  第一个建议,「黄金构图点」,其实也没有那么高大上啦,说实话我至今也不是很熟悉「黄金分割」,但是简化的情况下,如上图的四个红点,即为「黄金构图点」,我们在拍照时,可以尝试把主体放在那四个点的其中一点上,这样一来,便符合人们视觉习惯,使得照片看起来比较好看。

  第二个建议,地平线原则,在大多数的拍照过程中,我建议大家都根据九宫格所提供的水平线比一比,使得画面不倾斜,无论是地面、海、还是楼房等等,「大多数」情况下,都建议遵循这个地平线原则。

  第三个建议,多尝试正方形比例的构图,这个建议在后期裁剪来做,目的是让构图看起来更着重点。

网站推荐

  最后一个建议,但是也是最重要的一个建议:多看、多拍。

  对于多拍,自然是要大家自己平时走到哪儿就拍到哪儿,尽可能的多练习。

  这里我对于「多看」推荐几个优秀的网站供大家参考,其中包括摄影网站、设计网站等,之所以有非摄影网站,我是建议大家什么都看,只要是优秀的,电影、音乐、设计作品都有益的。

sy_13

结语

  简单的几个小建议,希望对大家有用。

  如果大家觉得还可以,请点个喜欢或关注,多谢大家。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

常见的几个系统广播

系统启动完成

  我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能。要实现这个功能,我们就可以订阅系统“启动完成”这条广播,接收到这条广播后我们就可以启动自己的服务了。我们来看一下BootCompleteReceiver和MsgPushService的具体实现:

1
2
3
4
5
6
7
8
9
10
11
public class BootCompleteReceiver extends BroadcastReceiver {  

private static final String TAG = "BootCompleteReceiver";

@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MsgPushService.class);
context.startService(service);
Log.i(TAG, "Boot Complete. Starting MsgPushService...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MsgPushService extends Service {  

private static final String TAG = "MsgPushService";

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate called.");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand called.");
return super.onStartCommand(intent, flags, startId);
}

@Override
public IBinder onBind(Intent arg0) {
return null;
}
}

  然后我们需要在 AndroidManifest.xml 中配置相关信息:

1
2
3
4
5
6
7
8
9
10
<!-- 开机广播接受者 -->  
<receiver android:name=".BootCompleteReceiver">
<intent-filter>
<!-- 注册开机广播地址-->
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- 消息推送服务 -->
<service android:name=".MsgPushService"/>

  我们看到 BootCompleteReceiver 注册了 “android.intent.action.BOOT_COMPLETED” 这个开机广播地址,从安全角度考虑,系统要求必须声明接收开机启动广播的权限,于是我们再声明使用下面的权限:

1
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

网络状态

  在某些场合,比如用户浏览网络信息时,网络突然断开,我们要及时地提醒用户网络已断开。要实现这个功能,我们可以接收网络状态改变这样一条广播,当由连接状态变为断开状态时,系统就会发送一条广播,我们接收到之后,再通过网络的状态做出相应的操作。下面就来实现一下这个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class NetworkStateReceiver extends BroadcastReceiver {

private static final String TAG = "NetworkStateReceiver";

@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "network state changed.");
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "network disconnected!", 0).show();
}
}

/**
* 网络是否可用
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] info = mgr.getAllNetworkInfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
}

  然后我们需要在 AndroidManifest.xml 中再注册一下这个广播接收者的信息:

1
2
3
4
5
6
<receiver android:name=".NetworkStateReceiver">  
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

  因为在 isNetworkAvailable 方法中我们使用到了网络状态相关的 API,所以需要声明相关的权限才行,下面就是对应的权限声明:

1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

电量变化

  如果我们阅读软件,可能是全屏阅读,这个时候用户就看不到剩余的电量,我们就可以为他们提供电量的信息。要想做到这一点,我们需要接收一条电量变化的广播,然后获取百分比信息,这听上去挺简单的,我们就来实现以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BatteryChangedReceiver extends BroadcastReceiver {  

private static final String TAG = "BatteryChangedReceiver";

@Override
public void onReceive(Context context, Intent intent) {
// 当前电量
int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
// 总电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int percent = currLevel * 100 / total;
Log.i(TAG, "battery: " + percent + "%");
}

}

  然后我们需要在 AndroidManifest.xml 中再注册一下这个广播接收者的信息:

1
2
3
4
5
6
<receiver android:name=".BatteryChangedReceiver">  
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

  当然,有些时候我们是要立即获取电量的,而不是等电量变化的广播,比如当阅读软件打开时立即显示出电池电量。我们可以按以下方式获取:

1
2
3
4
5
Intent batteryIntent = getApplicationContext().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));  
int currLevel = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int total = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int percent = currLevel * 100 / total;
Log.i("battery", "battery: " + percent + "%");

监听SD卡状态

  清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播

1
2
3
4
5
6
7
8
<receiver android:name="com.leoyanblog.sdcradlistener.SDCardReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<action android:name="android.intent.action.MEDIA_REMOVED"/>
<data android:scheme="file"/>
</intent-filter>
</receiver>

  广播接收者的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SDCardReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 区分接收到的是哪个广播
String action = intent.getAction();

if(action.equals("android.intent.action.MEDIA_MOUNTED")){
System.out.println("sd卡就绪");
}
else if(action.equals("android.intent.action.MEDIA_UNMOUNTED")){
System.out.println("sd卡被移除");
}
else if(action.equals("android.intent.action.MEDIA_REMOVED")){
System.out.println("sd卡被拔出");
}
}
}

监听应用的安装、卸载、更新

  应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名,清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播

1
2
3
4
5
6
7
8
<receiver android:name="com.leoyanblog.app.AppReceiver">
<intent-filter >
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>

  广播接收者的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AppReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//区分接收到的是哪种广播
String action = intent.getAction();
//获取广播中包含的应用包名
Uri uri = intent.getData();
if(action.equals("android.intent.action.PACKAGE_ADDED")){
System.out.println(uri + "被安装了");
}
else if(action.equals("android.intent.action.PACKAGE_REPLACED")){
System.out.println(uri + "被更新了");
}
else if(action.equals("android.intent.action.PACKAGE_REMOVED")){
System.out.println(uri + "被卸载了");
}
}
}

常见的 Action 常量

  • ACTION_TIME_CHANGED:系统时间被改变。

  • ACTION_DATE_CHANGED:系统日期被改变。

  • ACTION_TIMEZONE_CHANGED:系统时区被改变。

  • ACTION_BOOT_COMPLETED:系统启动完成。

  • ACTION_PACKAGE_ADDED:系统添加包。

  • ACTION_PACKAGE_CHANGED:系统的包改变。

  • ACTION_PACKAGE_REMOVED:系统的包被删除。

  • ACTION_PACKAGE_RESTARTED:系统的包被重启。

  • ACTION_PACKAGE_DATA_CLEARED:系统的包数据被清空。

  • ACTION_BATTERY_CHANGED:电池电量改变。

  • ACTION_BATTERY_LOW:电池电量低。

  • ACTION_POWER_CONNECTED:系统连接电源。

  • ACTION_POWER_DISCONNECTED:系统与电源断开。

  • ACTION_SHUTDOWN:系统被关闭。

  通过使用BroadcastReceiver来监听特殊的广播,就可以让应用随系统执行特定的操作。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

BroadcastReceiver 是什么

  BroadcastReceiver 是 Android 四大组件之一,本质是一种全局的监听器,用于监听系统或者应用全局的广播消息,然后根据广播信息做出相应的逻辑处理,也可以用来传输少量、频率低的数据。 因此它可以非常方便的实现不同组件之间的通信。

BroadcastReceiver 的生命周期

  由于 BroadcastReceiver 本质上属于一个监听器,因此实现 BroadcastReceiver 的方法也十分简单,每次系统 Broadcast 事件发生后,系统就会创建对应的 BroadcastReceiver 的实例,并且自动触发他的 onReceive() 方法,onReceive() 方法执行完后,BroadcastReceiver 的实例就会被销毁。也就是说 BroadcastReceiver 的生命周期就是 onReceive() 这个方法。

  如果 BroadcastReceiver 的 onReceive() 方法不能在10秒内执行完成,Android 会认为该程序无响应。所以不要在 BroadcastReceiver 的 onReceive() 方法中执行一些耗时操作,否则会弹出 ANR 的对话框。若必须要执行比较耗时的操作,则要考虑由当前 BroadcastReceiver 启动新的 Service 来完成操作。但不能绑定 Service。

  如果我们在 Activity 中注册了 BroadcastReceiver,当这个 Activity 销毁的时候要主动撤销注册否则会出现异常。

BroadcastReceiver 的创建

  首先,我们来演示一下创建一个 BroadcastReceiver,并让这个 BroadcastReceiver 能够根据我们的需要来运行。

  要创建自己的 BroadcastReceiver 对象,我们需要继承 android.content.BroadcastReceiver,并实现其 onReceive 方法。下面我们就创建一个名为 MyReceiver 广播接收者:

1
2
3
4
5
6
7
8
9
10
11
public class MyReceiver extends BroadcastReceiver {  

private static final String TAG = "MyReceiver";

@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.i(TAG, msg);
}

}

  在 onReceive 方法内,我们可以获取随广播而来的 Intent 中的数据,这非常重要,就像无线电一样,包含很多有用的信息。
在创建完我们的 BroadcastReceiver 之后,还不能够使它进入工作状态,我们需要为它注册一个指定的广播地址。没有注册广播地址的 BroadcastReceiver 就像一个缺少选台按钮的收音机,虽然功能俱备,但也无法收到电台的信号。下面我们就来介绍一下如何为 BroadcastReceiver 注册广播地址。

BroadcastReceiver的注册

  BroadcastReceiver 的注册方式有且只有两种,一种是静态注册(推荐使用),另外一种是动态注册,广播接收者在注册后就开始监听系统或者应用之间发送的广播消息。

静态注册

  静态注册是在 AndroidManifest.xml 文件中配置的,在 application 里面定义 receiver 并设置要接收的 action。我们就来为 MyBroadcastReceiver 注册一个广播地址:

1
2
3
4
5
6
<receiver android:name=".MyBroadcastReceiver">  
<intent-filter android:priority = "777">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>

  这里的 priority 取值是 -1000 到 1000,值越大优先级越高。配置了以上信息之后,只要是 android.intent.action.MY_BROADCAST 这个地址的广播,MyBroadcastReceiver 都能够接收的到。

  要销毁掉静态注册的广播接收者,可以通过调用 PackageManager 将 Receiver 禁用。

动态注册

  动态注册需要在代码中动态的指定广播地址并注册,通常我们是在 Activity 或 Service 中声明 BroadcastReceiver 的扩展对象,在 onResume 中注册,onPause 中卸载。下面我们就来看一下注册的代码:    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity {

MyBroadcastReceiver receiver;

@Override
protected void onResume() {
// 动态注册广播 (代码执行到这才会开始监听广播消息,并对广播消息作为相应的处理)
receiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED" );
registerReceiver( receiver , intentFilter);

super.onResume();
}

@Override
protected void onPause() {
// 撤销注册 (撤销注册后广播接收者将不会再监听系统的广播消息)
unregisterReceiver(receiver);
super.onPause();
}
}

注意,当这个 Activity 或 Service 被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,记得在特定的地方执行解除注册操作。

静态注册和动态注册的区别

  • 区别 1

  静态注册的广播接收者是一个常驻在系统中的全局监听器,也就是说无论应用是否处于运行状态,如果有广播信息传来,MyBroadcastReceiver 也会被系统调用而自动运行。

  动态注册方式与静态注册相反,不是常驻型的,也就是说广播会跟随程序的生命周期。

  • 区别 2

  当广播接收者通过 Intent 启动一个 Activity 或者 Service 时,如果 Intent 中无法匹配到相应的组件。动态注册的广播接收者将会导致应用报错。而静态注册的广播接收者将不会有任何报错,因为自从应用安装完成后,广播接收者跟应用已经脱离了关系。 

Broadcast 的类型

  发送广播主要有两种类型:

普通广播

  普通广播对于多个接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响。同级别的接收顺序先后是随机的;不同级别的接收顺序是级别高的先于级别低的收到广播;对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。   

有序广播

  有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接收者将处理好的数据再传播到优先级低的接收者那里,优先级高的接收者有能力调用 abortBroadcast() 来终止这个广播。

  同级别接收是先后是随机的,如果先接收到的把广播截断了,同级别的例外的接收者是无法收到该广播。

1
2
// 发送有序广播
sendOrderedBroadcast(intent, null);

  演示有序广播的流程,如下:

1
2
3
4
5
6
7
8
9
10
public void onReceive(Context arg0, Intent intent) {
  //获取上一个广播的bundle数据
  Bundle bundle = getResultExtras(true);//true:前一个广播没有结果时创建新的Bundle;false:不创建Bundle
  bundle.putString("key", "777");
  //将bundle数据放入广播中传给下一个广播接收者
  setResultExtras(bundle); 
  
  //终止广播传给下一个广播接收者
  abortBroadcast();
}

总结

  • 静态广播接收的处理器是由 PackageManagerService 负责,当手机启动或者新安装了应用的时候,PackageManagerService 会扫描手机中所有已安装的APP应用,将 AndroidManifest.xml 中有关注册广播的信息解析出来,存储至一个全局静态变量当中。

  • 动态广播接收的处理器是由 ActivityManagerService 负责,当 APP 的服务或者进程起来之后,执行了注册广播接收的代码逻辑,即进行加载,最后会存储在一个另外的全局静态变量中。需要注意的是:

  1、这个并非是一成不变的,当程序被杀死之后,已注册的动态广播接收器也会被移出全局变量,直到下次程序启动,再进行动态广播的注册,当然这里面的顺序也已经变更了一次。

  2、这里也并没完整的进行广播的排序,只记录注册的先后顺序,并未有结合优先级的处理。

  • 广播发出的时候,广播接收者的接收顺序如下:

  1、当广播为 普通广播 时,有如下的接收顺序:

    无视优先级

    动态优先于静态

    同优先级的动态广播接收器,先注册的大于后注册的

    同优先级的静态广播接收器,先扫描的大于后扫描的 

  2、当广播为 有序广播,那么会将动态广播处理器和静态广播处理器合并在一起处理广播的消息,最终确定广播接收的顺序: 

    优先级高的先接收

    同优先级的动静态广播接收器,动态优先于静态

    同优先级的动态广播接收器,先注册的大于后注册的

    同优先级的静态广播接收器,先扫描的大于后扫描的 

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

Android Studio 2.3 更新时出现的问题

  作为 Google 官方出版并维护的 IDE,被全球数以百万计的 Android 开发者钟爱并使用的开发工具,Android Studio,在 2017 年 3 月 2 日正式迎来了 2.3 稳定版的更新。

  更新新版 Android Studio 时,项目出现了小问题。

  刚打开项目时,项目提示如下:

image

  意思是 Android Gradle 插件的版本是否更新到 2.3.0 。点击更新后,项目的 build.gradle 文件的 gradle 版本号自动更改为 2.3.0。gradle-wrapper.properties 的 gradle 版本号自动更改为 3.3-all。文件如下图:

image

image

  结果项目构建时出现了如下错误:

image

  因为本项目以前用的是 2.14.1 版本的 gradle,所以想着改回去是不是能解决问题。结果又出现了问题。如下图:

image

  看来只能用 3.3 版本的了,然后去这个网址 https://services.gradle.org/distributions/ 下载了个 gradle-3.3-all.zip 文件。解压到 Android Studio 的安装目录下的 gradle 文件夹下,如下图:

image

  然后,在 Android Studio 的菜单栏,点击 File -> Setting -> Build,Execution,Deployment -> Gradle,设置 Gradle home 的路径,如下图:

image

  点击 Apply,点击 OK。然后重新构建项目,完美解决,一点没毛病。

本方法不一定适用所有项目,或者在过程中有什么其他问题,请关注微信公众号 LeoYan 来一起讨论。

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

什么是 Service?  

  Service 是 Android 系统的主要组件之一,Service 的职责是进行一些不需要UI存在的操作,比如网络请求、本地磁盘IO、执行定时任务等。Service 是没有图形界面的,只会在后台默默地运行。

注:Service 只是在后台运行的一个组件,默认情况下,Service 也是运行在 app 的主线程中的,它并不会开启新的线程或者进程(当然,这个可以做到)。所以,假如在 Service 中执行有耗时的操作的话,最好在子线程中执行。

Service 的特点

  没有可视的UI、后台运行,运行不阻塞前台UI、拥有服务的进程具有较高的优先级。

Service 与 Thread 的区别

  • Thread

  Thread 是程序执行的最小单元,它是分配 CPU 的基本单位。可以用 Thread 来执行一些异步的操作。

  • Service

  Service 是 Android 的一种机制,当它运行的时候如果是 Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是 Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!

  既然这样,那么我们为什么要用 Service 呢?其实这跟 Android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。

  举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有 start 的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。

  因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService()、Context.stopService()、Context.bindService(),Context.unbindService(),来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。

Service 的分类

按运行分类

  • 前台服务

  前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。

  例如,用服务来播放音乐的播放器就应该运行在前台,因为用户会清楚地知晓它的运行情况。 状态栏通知可能会标明当前播放的歌曲,并允许用户启动一个 Activity 来与播放器进行交互。

  要把你的服务请求为前台运行,可以调用 startForeground() 方法。此方法有两个参数:唯一标识通知的整数值、状态栏通知 Notification 对象。例如:

1
2
3
4
5
6
7
8
9
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());

Intent notificationIntent = new Intent(this,ExampleActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);

startForeground(ONGOING_NOTIFICATION, notification);

  要从前台移除服务,请调用 stopForeground() 方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知。此方法不会终止服务。不过,如果服务在前台运行时被你终止了,那么通知也会同时被移除。

  • 后台服务

按使用分类  

  • 本地服务

  用于应用程序内部,实现一些耗时任务,并不占用应用程序比如 Activity 所属线程,而是单开线程后台执行。

  调用 Context.startService() 启动,调用 Context.stopService() 结束。在内部可以调用 Service.stopSelf() 或 Service.stopSelfResult() 来自己停止。

  • 远程服务

  用于 Android 系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用 Context.bindService() 方法建立连接,并启动,以调用 Context.unbindService() 关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService() 会先加载它。

Service生命周期

  Service生命周期

  Android Service 的生命周期并不像 Activity 那么复杂,它只继承了 onCreate(), onStart(), onDestroy() 三个方法,当我们第一次启动 Service 时,先后调用了 onCreate(), onStart() 这两个方法,当停止 Service 时,则执行 onDestroy() 方法,这里需要注意的是,如果 Service 已经启动了,当我们再次启动 Service 时,不会在执行 onCreate() 方法,而是直接执行 onStart() 方法,具体的可以看下面的实例。

Service 生命周期方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ExampleService extends Service {
int mStartMode; // 标识服务被杀死后的处理方式
IBinder mBinder; // 用于客户端绑定的接口
boolean mAllowRebind; // 标识是否使用onRebind

@Override
public void onCreate() {
// 服务正被创建
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 服务正在启动,由startService()调用引发
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// 客户端用bindService()绑定服务
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// 所有的客户端都用unbindService()解除了绑定
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// 某客户端正用bindService()绑定到服务,
// 而onUnbind()已经被调用过了
}
@Override
public void onDestroy() {
// 服务用不上了,将被销毁
}
}

请注意 onStartCommand() 方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand() 的返回值必须是以下常量之一:

START_NOT_STICKY

  如果系统在 onStartCommand() 返回后杀死了服务,则不会重建服务了,除非还存在未发送的 intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。 

START_STICKY

  如果系统在 onStartCommand() 返回后杀死了服务,则将重建服务并调用 onStartCommand(),但不会再次送入上一个 intent, 而是用 null intent 来调用 onStartCommand()。除非还有启动服务的 intent 未发送完,那么这些剩下的 intent 会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。

START_REDELIVER_INTENT

  如果系统在 onStartCommand() 返回后杀死了服务,则将重建服务并用上一个已送过的 intent 调用 onStartCommand()。任何未发送完的 intent 也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。

  服务的生命周期与 Activity 的非常类似。不过,更重要的是你需密切关注服务的创建和销毁环节,因为后台运行的服务是不会引起用户注意的。

  通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

  • 服务的整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。

  无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate() 和 onDestroy() 方法。

  • 服务的有效生命周期从调用 onStartCommand() 或 onBind() 方法开始。每种方法均有 Intent 对象传入,该对象分别传递到 startService() 或 bindService()。

  对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

注:尽管启动服务是通过调用 stopSelf() 或 stopService() 来停止,但是该服务并无相应的回调(没有 onStop() 回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁 — onDestroy() 是接收到的唯一回调。

在manifest中声明服务

  如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。

  要声明服务,请添加 元素作为 元素的子元素。例如:

1
2
3
4
5
6
7
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

Service 元素的属性有:

android:name  ————-  服务类名

android:label  ————–  服务的名字,如果此项不设置,那么默认显示的服务名则为类名

android:icon  ————–  服务的图标

android:permission  ——-  申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务

android:process  ———-  表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字

android:enabled  ———-  如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false

android:exported  ———  表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false 

  android:name 是唯一必需的属性——它定义了服务的类名。与 activity 一样,服务可以定义 intent 过滤器,使得其它组件能用隐式 intent 来调用服务。如果你想让服务只能内部使用(其它应用程序无法调用),那么就不必(也不应该)提供任何 intent 过滤器。

  此外,如果包含了 android:exported 属性并且设置为 “false”, 就可以确保该服务是你应用程序的私有服务。即使服务提供了 intent 过滤器,本属性依然生效。 

Service 启动方式

startService()

  从 Activity 或其它应用程序组件中可以启动一个服务,调用 startService() 并传入一个Intent(指定所需启动的服务)即可。

1
2
	Intent intent = new Intent(this, MyService.class);
startService(intent);

服务类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MyService extends Service {

/**
* onBind 是 Service 的虚方法,因此我们不得不实现它。
* 返回 null,表示客服端不能建立到此服务的连接。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)    {
//接受传递过来的intent的数据
return START_STICKY;
};

@Override
public void onDestroy() {
super.onDestroy();
}

}

  一个 started 服务必须自行管理生命周期。也就是说,系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。 因此,服务必须通过调用 stopSelf() 自行终止,或者其它组件可通过调用 stopService() 来终止它。

bindService()  

  当应用程序中的 Activity 或其它组件需要与服务进行交互,或者应用程序的某些功能需要暴露给其它应用程序时,你应该创建一个 bound 服务,并通过进程间通信(IPC)来完成。

方法如下:

1
2
Intent intent=new Intent(this,BindService.class); 
bindService(intent, ServiceConnection conn, int flags)

注意 bindService 是 Context 中的方法,当没有 Context 时传入即可。

在进行服务绑定的时,其 flags 有:

  • Context.BIND_AUTO_CREATE

  表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁 

  • Context.BIND_DEBUG_UNBIND  

  通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用

  • Context.BIND_NOT_FOREGROUND  

  表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。

服务类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class BindService extends Service {

// 实例化MyBinder得到mybinder对象;
private final MyBinder binder = new MyBinder();

/**
* 返回Binder对象。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return binder;
}

/**
* 新建内部类MyBinder,继承自Binder(Binder实现IBinder接口),
* MyBinder提供方法返回BindService实例。
*/
  public class MyBinder extends Binder{

public BindService getService(){
return BindService.this;
}
}


@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
}

启动服务的 Activity 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MainActivity extends Activity {

/** 是否绑定 */
boolean mIsBound = false;
BindService mBoundService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBindService();
}
/**
* 实例化ServiceConnection接口的实现类,用于监听服务的状态
*/
private ServiceConnection conn = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BindService mBoundService = ((BindService.MyBinder) service).getService();

}

@Override
public void onServiceDisconnected(ComponentName name) {
mBoundService = null;

}
};

/** 绑定服务 */
public void doBindService() {
bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE);
mIsBound = true;
}

/** 解除绑定服务 */
public void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(conn);
mIsBound = false;
}
}

@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();

doUnbindService();
}
}

注意 在AndroidMainfest.xml 中对 Service 进行显式声明

判断 Service 是否正在运行:

1
2
3
4
5
6
7
8
9
10
private boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

 {
if ("com.example.demo.BindService".equals(service.service.getClassName()))   {
return true;
}
}
return false;
}

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

Activity的任务和启动模式

任务与返回栈

后进先出原则

后进先出

  Task 就好像是能包含很多 Activity 的栈。默认情况下,一个 Activity 启动另外一个 Activity 时,两个 Activity 是放在同一个 Task 栈中的,第二个 Activity 压入第一个 Activity 所在的 Task 栈。当用户按“返回”按钮时,第二 Activity 从栈中弹出,第一个 Activity 恢复执行。直到用户返回主屏幕为止(或者,返回 Task 中正在运行的任意 Activity)。当所有 Activity 均从堆栈中移除后,Task 即不复存在。

多任务

多任务

  Task 是一个有机整体,当用户开始新 Task 或通过“主页”按钮转到主屏幕时,可以移动到“后台”。 尽管在后台时,该任务中的所有 Activity 全部停止,但是 Task 的返回栈仍旧不变,也就是说,当另一个 Task 发生时,该 Task 仅仅失去焦点而已,如上图中所示。然后, Task 可以返回到“前台”,用户就能够回到离开时的状态。

注:后台可以同时运行多个 Task 。但是,如果用户同时运行多个后台 Task ,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。

可多次实例化

多次实例化

  应用中的一个 Activity 可能会多次实例化,如上图所示。因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。

任务的默认行为总结

  • 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。
  • 用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。 系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。
  • 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。 堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统不会保留该 Activity 的状态。
  • 即使来自其他任务,Activity 也可以多次实例化。

LaunchMode简介

  LaunchMode 是 Activity 类的一个属性,该属性包括4个具体值:standard、singleTop、singleTask、singleInstance。我们都知道,Android 系统中启动 Activity 是通过 Intent 实例进行的,当系统收到一个 Intent 的实例需要去启动指定的 Activity 的时候,Android 系统会根据目标 Activity 的该属性值来决定是要要创建新的该 Activity 实例以及如何在 Task 中创建该 Activity 的实例。这就是 LaunchMode 这个属性的作用。

LaunchMode 分为两个类别

  • 普通类型

  用户常用的启动模式类型,大部分 Activity都是这两种启动模式。包括 standard 和 singleTop 两种启动模式

  • 特殊类型

  具有特殊的行为的启动模式,只针对特殊需求的使用。包括 singleTask 和 singleInstance 两种启动模式

官方文档对4中启动模式描述如下:

使用场景 启动模式 是否可以有多个实例? 简介
普通类型 standard 系统默认的启动模式,当系统接收到一个 Intent 实例去启动一个 standard 模式的 Activity 时,系统总是会在目标栈的顶部创建一个新的 Activity 实例,并把 Intent 的实例传进去。
普通类型 singleTop 视具体情况 当系统接收到一个 Intent 实例去启动一个 singleTop 模式的 Activity 时,如果在目标栈的顶部存在一个该 Activity 的实例的话,那么系统就会重用这个 Activity 的实例而不创建新的实例,并回调该 Activity 的 onNewIntent(Intent intent) 方法把新的 Intent 实例当作方法参数传递进去;如果在目标栈的顶部没有该 Activity 的实例的话系统将会在新建一个 Activity 实例,与 standard 的行为就一样了。
特殊类型 singleTask 当系统接收到一个 Intent 实例去启动一个 singleTask 模式的 Activity 时,如果不存在该 Activity 的实例的话,系统会先创建一个新的 Task,并在该 Task 底部里面创建一个该 Activity 的实例,随后把 Intent 实例传递进去;如果已经存在一个该 Activity 的实例的话,系统就不会再创建新的实例,那么系统就会重用这个 Activity 的实例而不创建新的实例,并回调该 Activity 的 onNewIntent(Intent intent) 方法把新的 Intent 实例当作方法参数传递进去,同时,该 Activity 实例所在的 Task 将会被调到前台。
特殊类型 singleInstance 类似于 singleTask ,唯一不同的地方在于,singleInstance 的 Activity 不允许自己的 Task 中存在其他的 Activity 实例,也就是说 singleInstance 的 Activity 永远是 Task 中唯一的一个 Activity 实例。

LaunchMode 的定义

  • 静态定义:在目标 Activity 的 mainfest 中使用标签静态定义
  • 动态定义:在启动目标 Activity 的 Intent 对象中使用不同的 flag 定义,可以用来定义 LaunchMode 的 flag 有以下三个:
flag 对应的 LaunchMode
FLAG_ACTIVITY_NEW_TASK singleTask
FLAG_ACTIVITY_SINGLE_TOP singleTop
FLAG_ACTIVITY_CLEAR_TOP 无对应的LaunchMode

注:在Activity A启动 Activity B的时候,假如B已经在 mainfest 文件中定义过 LaunchMode,此时的 Intent 对象中也有定义 LaunchMode,这时候以 Activity A start B 的时候以 Intent 中定义的为准。

典型使用场景

LaunchMode 使用场景
standard 绝大多数标准的跳转
singleTop 新闻阅读类的内容页面,假如在一个新闻页面又打开了另一个新闻页面,这时候Task的顶端已经有该Activity的实例了,就不用再初始化了
singleTask 通知栏启动其他app,一般都会在Intent中加上FLAG_ACTIVITY_NEW_TASK,相当于使用了singleTask

一些会影响 Task 行为的 mainfest 中 Activity 的属性

属性名称 可取值 意义
android:taskAffinity 一个存在的包名 表示该 Activity 与该包名具有亲缘,在该 Activity 启动的时候,会优先选择被添加进已经存在的具有亲缘关系的 Task 中。
android:allowTaskReparenting true, false 如果一个 Activity 的该属性值是 true,它先被一个外部 app 启动了,此时它存在于那个调用它的 app 的 Task 中,当与它有亲缘关系的 Task 被后台带回到前台的时候,系统会把它换到该 Task 中;如果值为 false 的话,就不会发生移动。
android:alwaysRetainTaskState true, false 正常情况下,当一个 Task 在后台待了很久之后,系统会把该 Task 的非根 Activity都清除,只保留根 Activity。但是假如根 Activity 的该属性值被设置为 true 的话,系统会保留该 Task 中所有的 Activity。

LaunchMode 总结

  • LaunchMode 的作用,是让开发者可以在一定程度上决定自己的 Activity 该如何被系统实例化,从而来满足开发者的开发需求。

  • LaunchMode 一共分为两类,一类是普通开发者最常用的:standard 和 singleTop,其中 standard 是官方定义的最常见的也是默认的系统对 Activity 进行实例化的行为模式,singleTop 在 standard 的基础上进行了点小的改动,当目标栈的顶部有 Activity 的实例的时候将不再对该 Activity 进行初始化而是直接使用现成的 Activity 实例,因为有很多的使用场景是 standard 不太合适而 singleTop 比较合适的;另一个类别的是普通开发者不常使用的,singleTask 和 singleInstance,官方并不推荐在通常的场景中使用该类的 LaunchMode,只有在极少数特殊的情形中才去使用,该类 LaunchMode 的特点是 Activity 在系统中只会有一个实例存在,不同的地方在于 singleInstance 的 Activity 会独占一个 Task,而 singleTask 的则不会。

  • LaunchMode 可以在 mainfest 文件中静态地设置,同时,在代码中,通过 Intent 的几个 flag 也可以达到设置 LaunchMode 的目的。假如某一次启动一个 Activity 的时候,在 Intent 中指定了一个 LaunchMode,而且该 Activity 本身已经在 mainfest 文件中设置了 LaunchMode,那么这个时候系统会 以Intent 中指定的为准。Android 系统这样设计的目的,我想是为了让整个系统的 Activity 可以更方便地让其它 Activity(可以不再一个app内)进行调用,这与 Android 的开放的态度是一致的。