愿历经千帆,归来仍少年


  • 首页

  • 归档

  • 标签

  • 搜索

简单工厂模式

发表于 2017-11-26

简单工厂模式

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有 共同的父类 。因为在简单工厂模式中是用静态方法来创建实例,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式。

简单工厂模式有如下几个角色:
● Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Factory {
//静态工厂方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
//初始化设置product
}
else if (arg.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
//初始化设置product
}
return product;
}
}

● Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。

1
2
3
4
5
6
7
8
abstract class Product {
//所有产品类的公共业务方法
public void methodSame() {
//公共方法的实现
}
//声明抽象业务方法
public abstract void methodDiff();
}

● ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。

1
2
3
4
5
6
class ConcreteProduct extends Product {
//实现业务方法
public void methodDiff() {
//业务方法的实现
}
}

相关案例实现:

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
具体实现一:
interface Chart{
public void display();
}
public class pie implments Chart{
public void display(){
System.out.println("display with pie")
}
}
public class histogram implements Chart{
public void display(){
System.out.println("display with histogram")
}
}
class ChartFactory{
public static Chart getChart(String type){
Chart chart = null;
if(type.equals("pie")){
chart = new pie();
}else if(type.equals("histogram")){
chart = new histogram();
}else{
}
return chart;
}
}

引入配置文件和工具类XMLUtil之后,客户端代码修改如下:

1
2
3
4
5
6
7
8
class Client {
public static void main(String args[]) {
Chart chart;
String type = XMLUtil.getChartType(); //读取配置文件中的参数
chart = ChartFactory.getChart(type); //创建产品对象
chart.display();
}
}

虽然简单工厂模式实现了对象的创建和使用分离,但是仍然存在如下两个问题:
(1) 工厂类过于庞大,包含了大量的if…else…代码,导致维护和测试难度增大;

(2) 系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了“开闭原则”。

工厂方法模式

定义一个用于创建对象(抽象工厂)的接口,让子类决定将哪一个类实例化。
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。

在工厂方法模式结构图中包含如下几个角色:
● Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
● ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
● Factory(抽象工厂):在抽象工厂类中,声明了工厂方法,用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。

1
2
3
interface Factory{
public Product factoryMethod();
}

● ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

1
2
3
4
5
class ConcreteFactory implements Factory{
public Product factoryMethod() {
return new ConcreteProduct();
}
}

在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等
工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

ps:面向对象编程补充

继承使用的三大要点:
1.父类只给子类提供服务,并不涉及子类的业务逻辑
Java中Object并不影响Model View Controller的执行逻辑和业务,Object为子类提供基础服务,例如内部计数等

2.层次关系明显,功能划分清晰,父类和子类各做各的。
Object并不参与MVC的管理中,那些都只是各自派生类自己要处理的事情。

3.父类的所有变化,都需要在子类中体现,也就是说此时耦合已经成为了需求。

结论:万不得已不要用继承,优先考虑组合。

多态:多态一般都要和继承结合起来,其本质是子类通过覆盖父类的方法,来使得同一对象同一方法的调用产生不同的结果。

使用多态的两个要素:
1. 如果引入多态之后导致对象角色不够单纯,那就不应该引入多态;如果引入多态之后依然是单纯角色,则可以使用多态。
2. 如果要覆重的方法是角色业务的其中一个组成部分,那么就最好不要用多态的方案,用IOP,因为在外界调用的时候其实并不需要通过多态来满足定制化的需求。
多态在面向对象程序中的应用相当广泛,只要有继承的地方,或多或少都会用到多态。然而多态比起继承来,更容易被不明不白地使用,一切看起来都那么顺其自然。在客户程序员这边,一般是只要多态是可行方案的一种,到最后大部分都会采用多态的方案来解决问题。
然而多态正如它名字中所暗示的,它有非常大的潜在可能引入不属于对象初衷的逻辑,巨大的灵活性也导致客户程序员在面对问题的时候不太愿意采用其他相对更优的方案,比如IOP。在决定是否采用多态时,我们要有一个清晰的角色概念,做好角色细分,不要角色混乱。该是拦截器的,就给他制定一个拦截器接口,由另一个对象(逻辑上的另一个对象,当然也可以是自己)去实现接口里的方法集。不要让一个对象在逻辑上既是拦截器又是业务模块。这样才方便未来的维护。另外也要注意被覆重方法的作用,如果只是单纯为了提供父类所需要的中间数据的,一律都用IOP,这是比直接采用多态更优的方案。

适配器模式

发表于 2017-11-26

适配器模式–不兼容结构的协调

将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种:
在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系

对象适配器:

在对象适配器模式结构中有以下几个角色:
● Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

● Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心。在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

● Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

1
2
3
4
5
6
7
8
9
10
11
class Adapter extends Target { //继承目标抽象类Target,并重写其request方法
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee; //在适配器中实例化适配者对象
}
public void request() {
adaptee.specificRequest(); //转发调用,即实现request()接口和specialRequest()接口的适配。
}
}

注:client希望希望调用request()接口来实现其所需的业务逻辑,Adaptee类的specialRequest()接口刚好可以完成其业务逻辑(其源代码是无法获取的),此时需要上面的这种适配。上面的Adapter和Adaptee是一种关联关系(委派关系)。

类适配器:

类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同,对象适配器模式中适配器和适配者之间是关联关系,而类是配器模式中适配器和适配者是继承关系。
典型的类适配器代码如下所示:

1
2
3
4
5
class Adapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}

适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。
由于Java、C#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adapter为最终(Final)类,也无法使用类适配器。在Java等面向对象编程语言中,大部分情况下我们使用的是对象适配器,类适配器较少使用。

适配器模式的总结:
适配器模式将现有接口转化为客户类所希望的接口,实现了对现有类的复用。在软件开发中得以广泛应用,在Spring等开源框架驱动程序设计中也使用了适配器模式。

适用场景
在以下情况下可以考虑使用适配器模式:
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

service基础

发表于 2017-11-12

Service

Service通常总是称之为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件:

1.并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的);

2.具有较长时间的运行特性。

Service AndroidManifest.xml 声明

一般而言,从Service的启动方式上,可以将Service分为Started Service和Bound Service。无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来。在使用Service时,要想系统能够找到此自定义Service,无论哪种类型,都需要在AndroidManifest.xml中声明,语法格式如下:

1
2
3
4
5
6
7
8
9
10
1 <service android:enabled=["true" | "false"]
2 android:exported=["true" | "false"]
3 android:icon="drawable resource"
4 android:isolatedProcess=["true" | "false"]
5 android:label="string resource"
6 android:name="string"
7 android:permission="string"
8 android:process="string" >
9 . . .
10 </service>

Started Service生命周期及进程相关

1.onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional … >> onDestroy(Client调用stopService(..))

注:onStartCommand(..)可以多次被调用,onDestroy()与onCreate()想匹配,当用户强制kill掉进程时,onDestroy()是不会执行的。

2.对于同一类型的Service,Service实例一次永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。

3.Service通过startService(..)启动Service后,此时Service的生命周期与Client本身的什么周期是没有任何关系的,只有Client调用stopService(..)或Service本身调用stopSelf(..)才能停止此Service。当然,当用户强制kill掉Service进程或系统因内存不足也可能kill掉此Service。

4.Client A 通过startService(..)启动Service后,可以在其他Client(如Client B、Client C)通过调用stopService(..)结束此Service。

5.Client调用stopService(..)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(..)无需做当前Service是否有效的判断。

6.startService(Intent serviceIntent),其中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent。当处于不同App时,只能使用隐式Intent。

当Service需要运行在单独的进程中,AndroidManifest.xml声明时需要通过android:process指明此进程名称,当此Service需要对其他App开放时,android:exported属性值需要设置为true(当然,在有intent-filter时默认值就是true)。

1
2
3
4
5
6
7
8
1 <service
2 android:name=".MyService"
3 android:exported="true"
4 android:process=":MyCorn" >
5 <intent-filter>
6 <action android:name="com.example.androidtest.myservice" />
7 </intent-filter>
8 </service>

Service和Activity通信

Service类中有一个onBind()方法就是用于和Activity建立关联的。

Service和Thread的关系

Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。
然而Service是运行在主线程里的,也就是说如果你在Service里编写了非常耗时的代码,程序可能就会出现ANR。Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。额,既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

一个比较标准的Service就可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行后台任务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
// 执行具体的下载任务
}
}).start();
}
}

前台Service

Service几乎都是在后台运行的,但当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

Bound Service

Bound Service的主要特性在于Service的生命周期是依附于Client的生命周期的,当Client不存在时,Bound Service将执行onDestroy,同时通过Service中的Binder对象可以较为方便进行Client-Service通信。Bound Service一般使用过程如下:

1.自定义Service继承基类Service,并重写onBind(Intent intent)方法,此方法中需要返回具体的Binder对象;

2.Client通过实现ServiceConnection接口来自定义ServiceConnection,并通过bindService (Intent service, ServiceConnection sc, int flags)方法将Service绑定到此Client上;

3.自定义的ServiceConnection中实现onServiceConnected(ComponentName name, IBinder binder)方法,获取Service端Binder实例;

4.通过获取的Binder实例进行Service端其他公共方法的调用,以完成Client-Service通信;

5.当Client在恰当的生命周期(如onDestroy等)时,此时需要解绑之前已经绑定的Service,通过调用函数unbindService(ServiceConnection sc)。

在Bound Service具体使用过程中,根据onBind()方法返回的Binder对象的定义方式不同,又可以将其分为以下三种方式,且每种方式具有不同的特点和适用场景:

1) Extending the Binder class
这是Bound Service中最常见的一种使用方式,也是Bound Service中最简单的一种。

局限:Clinet与Service必须同属于同一个进程,不能实现进程间通信(IPC)。

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
/**
* Created by zhongxianfeng on 17-11-3.
*/
public class MyService extends Service {
public static final String TAG = "myService";
private MyBinder mBinder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
class MyBinder extends Binder{
public void startDownload() {
Log.d("TAG", "startDownload() executed");
}
}
public void onCreate(){
super.onCreate();
Log.d(TAG, "onCreate:executed");
}
public int onStartCommand(Intent intent,int flags,int startId){
Log.d(TAG, "onStartCommand: executed");
return super.onStartCommand(intent,flags,startId);
}
public void onDestroy(){
super.onDestroy();
Log.d(TAG, "onDestroy: executed");
}
}
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public class MainActivity extends AppCompatActivity implements OnClickListener {
public static final String TAG = "MainActivity";
private Button start_Service ;
private Button destroy_Service;
private Button bind_Service;
private Button unbind_Service;
private MyService.MyBinder myBinder;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
myBinder = (MyService.MyBinder) iBinder;
myBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate: MainActivity thread id"+Thread.currentThread().getId());
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
start_Service = (Button)findViewById(R.id.start_Service);
destroy_Service = (Button)findViewById(R.id.destroy_Service);
unbind_Service = (Button)findViewById(R.id.unbind_Service);
bind_Service =(Button)findViewById(R.id.bind_Service);
bind_Service.setOnClickListener(this);
start_Service.setOnClickListener(this);
destroy_Service.setOnClickListener(this);
unbind_Service.setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bind_Service:
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,serviceConnection,BIND_AUTO_CREATE);
break;
case R.id.unbind_Service:
unbindService(serviceConnection);
break;
case R.id.start_Service:
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
case R.id.destroy_Service:
Intent destroyIntent = new Intent(this,MyService.class);
stopService(destroyIntent);
break;
default:
break;
}
}
}

首次点击bind_Service按钮时,依次调用:onCreate —–> onBind —–>onServiceConnected方法。再次点击bindService按钮时,发现没有任何输出,说明MyService没有进行任何回调。
点击unbind_Service按钮进行unbindService()时,回调顺序为:onUnbind—–> onDestroy方法

2)Using a Messenger
Messenger,在此可以理解成”信使“,通过Messenger方式返回Binder对象可以不用考虑Clinet - Service是否属于同一个进程的问题,并且,可以实现Client - Service之间的双向通信。极大方便了此类业务需求的实现。

局限:不支持严格意义上的多线程并发处理,实际上是以队列去处理


参考: http://blog.csdn.net/guolin_blog/article/details/11952435/

http://www.cnblogs.com/lwbqqyumidi/p/4181185.html

回调

发表于 2017-11-12

什么是回调

回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百度百科中是这样的:
软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用.

  • 回调的概念:A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。举个例子就是,我们想要问别人一道题,我们把题跟对方说了一下,对方说好,等我做完这道题,我就告诉你,这个时候就用到了回调,因为我们并不知道对方什么时候会做完,而是对方做完了来主动找我们。

  • 同步回调
    代码运行到某一个位置的时候,如果遇到了需要回调的代码,会在这里等待,等待回调结果返回后再继续执行。

  • 异步回调
    代码执行到需要回调的代码的时候,并不会停下来,而是继续执行,当然可能过一会回调的结果会返回回来。

实例分析

现在来分析分析下Android View的点击方法onClick()。我们知道onclick()是一个回调方法,当用户点击View就执行这个方法,我们用Button来举例好了

1
2
3
4
5
6
7
8
9
10
11
12
//这个是View的一个回调接口
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
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
package com.example.zhongxianfeng.demo_service;
<!--
MainActivity实现了 OnClickListener接口,并重写了其onClick方法
-->
public class MainActivity extends AppCompatActivity implements OnClickListener {
public static final String TAG = "MainActivity";
private Button start_Service ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate: MainActivity thread id"+Thread.currentThread().getId());
start_Service = (Button)findViewById(R.id.start_Service);
<!--
MainActivity 调用View的方法,而Button extends View----->A类调用B类的某个方法 C
-->
start_Service.setOnClickListener(this);//此处传入的是MainActivity对象,其会向上转型为OnClickListener
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start_Service:
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
default:
break;
}
}
}

setOnClickListener方法对应的部分源码:

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
/**
* 这个View就相当于B类
*/
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
protected OnClickListener mOnClickListener;
/**
* setOnClickListener()的参数是OnClickListener接口------>背景三
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//这个不就是相当于B类调用A类的某个方法D,这个D就是所谓的回调方法。
mOnClickListener.onClick(this); //此处的mOnClickListener也就是MainActivity对象
return true;
}
return false;
}
}

策略模式

发表于 2017-10-28

策略模式(Strategy Pattern)

定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。
策略模式中包含以下几个角色:

  • Context(环境类)
    环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。 对于Context类而言,在它与抽象策略类之间建立一个关联关系,其典型代码如下所示:
1
2
3
4
5
6
7
8
9
10
class Context {
private AbstractStrategy strategy; //维持一个对抽象策略类的引用
public void setStrategy(AbstractStrategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
  • Strategy(抽象策略类)
    它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
1
2
3
abstract class AbstractStrategy{ //抽象类或者接口都可以
public abstract void algorithm();//声明抽象算法
}
  • ConcreteStrategy(具体策略类)
    它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
1
2
3
4
5
ConcreteStrategyA extends AbstractStrategy{
public void algorithm(){
//算法的具体实现
}
}

为了提高系统的灵活性和可扩展性,可以将具体策略类的类名存储在配置文件中,并通过工具类XMLUtil来读取配置文件并反射生成对象,XMLUtil类的代码如下所示:

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
<?xml version="1.0"?>
<config>
<className>StudentDiscount</className>
</config>
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
public static Object getBean() {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}

策略模式用于算法的自由切换和扩展,它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及算法的封装、复用和切换都可以考虑使用策略模式。符合面向对象的依赖倒置,开闭原则和里氏替换的设计原则

抽象类和接口

发表于 2017-10-28

抽象类和接口


抽象类和接口时Java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予Java强大的面向对象能力。

抽象类

在面向对象的领域Everything is Object,同时所有的对象都是通过类描述的,但是并不是所有的类都是来描述对象。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。抽象类体现了数据抽象的思想,是实现多态的一种机制。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有任何存在的意义。

在使用抽象类时需要注意几点:

  • 抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。
  • 抽象方法必须由子类来进行重写。
  • 只要包含一个抽象方法的抽象类,该类必须要定义成抽象类,不管是否还包含有其他方法。
  • 抽象类中可以包含具体的方法,当然也可以不包含抽象方法。
  • 子类中的抽象方法不能与父类的抽象方法同名。
  • abstract不能与final并列修饰同一个类。
  • abstract 不能与private、static、final或native并列修饰同一个方法。

接口

接口是一种比抽象类更加抽象的“类”。这里给“类”加引号是我找不到更好的词来表示,因为接口本身就不是类。接口是用来建立类与类之间的协议,它提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须实现该接口的所有方法,通过implements关键字,它表示该类在遵循某个或某组特定的接口。 接口时抽象类的延伸,Java是不能多重继承的,而接口弥补了抽象类不能多重继承的缺陷。
在使用接口过程中需要注意如下几个问题:

  • Interface的所有方法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!
  • 接口中可以定义“成员变量”,或者说是不可变的常量,因为接口中的“成员变量”会自动变为为public static final。可以通过类命名直接访问:ImplementClass.name。
  • 接口中不存在实现的方法
  • 不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。

抽象类与接口的区别

语法层次上,抽象类可以拥有任意范围的成员数据,同时也可以拥有自己的非抽象方法,但是接口中,它仅能够有静态,不能修改的成员数据,同时它所有方法必须是抽象的。

设计层次上:
1、 抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系。对于接口则不然,并不要求接口的实现和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。
3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。

案例分析:有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:

1
2
3
4
5
6
7
8
9
abstract class Door{
public void close();
public void open();
}
或者
interface Door{
void open();
void close();
}

但是现在如果我们需要门具有报警的功能,那么该如何实现呢???
解决方案一:给Door增加一个报警方法:clarm()

1
2
3
4
5
6
7
8
9
10
11
abstract class Door{
public void close();
public void open();
public void alarm();
}
或者
interface Door{
void open();
void close();
void alarm();
}

这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle),在Door的定义中把Door概念本身固有的行为方法和另外一个概念”报警器”的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为”报警器”这个概念的改变而改变,反之依然。

解决方案二
既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:
1、两个都使用抽象类来定义。
2、两个都使用接口来定义。
3、一个使用抽象类定义,一个是用接口定义。
由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。如果选择第二种都是接口来定义,那么就反映了两个问题:1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。

第三种,如果我们对问题域的理解是这样的:AlarmDoor本质上Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是门,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Door{
abstract void open();
abstract void close();
}
interface Alarm{
void alarm();
}
class AlarmDoor extends Door implements Alarm{
void open(){}
void close(){}
void alarm(){}
}

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实抽象类表示的是”is-a”关系,接口表示的是”like-a”关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

intent基础

发表于 2017-10-28

Intent 和 Intent 过滤器

Intent 是一个消息传递对象,其主要用来协助应用间的交互与通讯,你通过startActivity()方法发送一个Intent给系统,系统会根据这个Intent帮助你找到对应的Activity,即使这个Activity在其他的应用中,也可以用这种方法启动它。其基本用例主要包括以下三个:

  • 启动 Activity:
    Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。
    如果您希望在 Activity 完成后收到结果,请调用 startActivityForResult()。在 Activity 的 onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。
    对startActivity的调用最终会转到对startActivityForResult的调用,其源码如下:

  • 启动服务:
    Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。

  • 传递广播:
    广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。

Intent 类型

Intent 分为两种类型:

  • 显式 Intent:按名称(完全限定类名)指定要启动的组件。 通常,您会在自己的应用中使用显式 Intent 来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,启动新 Activity 以响应用户操作,或者启动服务以在后台下载文件。创建显式 Intent 启动 Activity 或服务时,系统将立即启动 Intent 对象中指定的应用组件。
    例如,如果在应用中构建了一个名为 DownloadService、旨在从网页下载文件的服务,则可使用以下代码启动该服务:
1
2
3
4
5
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Intent(Context, Class) 构造函数分别为应用和组件提供 Context 和 Class 对象。因此,此 Intent 将显式启动该应用中的 DownloadService 类。

  • 隐式 Intent :不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
    Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。 例如,通过为 Activity 声明 Intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 Intent 启动 Activity。同样,如果您没有为 Activity 声明任何 Intent 过滤器,则 Activity 只能通过显式 Intent 启动。
    例如,如果您希望用户与他人共享您的内容,请使用 ACTION_SEND 操作创建 Intent,并添加指定共享内容的 extra。 使用该 Intent 调用 startActivity() 时,用户可以选取共享内容所使用的应用。
1
2
3
4
5
6
7
8
9
10
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}

图 1. 隐式 Intent 如何通过系统传递以启动其他 Activity 的图解:[1] Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。[2] Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。 找到匹配项之后,[3] 该系统通过调用匹配 Activity(Activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配 Activity。

构建 Intent

Intent 对象携带了 Android 系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。

Intent 中包含的主要信息如下:

  • 组件名称
    要启动的组件名称。这是可选项,但也是构建显式 Intent 的一项重要信息,这意味着 Intent 应当仅传递给由组件名称定义的应用组件。 如果没有组件名称,则 Intent 是隐式的,且系统将根据其他 Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。 因此,如需在应用中启动特定的组件,则应指定该组件的名称。 Intent 的这一字段是一个 ComponentName 对象,您可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。 例如, com.example.ExampleActivity。您可以使用 setComponent()、setClass()、setClassName() 或 Intent 构造函数设置组件名称。
  • 操作
    指定要执行的通用操作(例如,“查看”或“选取”)的字符串。以下是一些用于启动 Activity 的常见操作:

ACTION_VIEW
如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请使用 Intent 将此操作与 startActivity() 结合使用。
ACTION_SEND
这也称为“共享”Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 将此操作与 startActivity() 结合使用。

  • 数据
    一个Uri对象,对应着一个数据,这个数据可能是MIME类型的。当创建一个intent时,除了要指定数据的URI之外,指定数据的类型(MIME type)也很重要,比如,一个activity能够显示照片但是无法播放视频,虽然启动Activity时URI格式很相似。

  • 类别
    一个包含应处理 Intent 组件类型的附加信息的字符串。
    您可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 均不需要类别。 以下是一些常见类别:

CATEGORY_BROWSABLE
目标 Activity 允许本身通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。
CATEGORY_LAUNCHER
该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。

  • Extra
    携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。 您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。

  • 标志
    在 Intent 类中定义的、充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。


补充内容

1 使用Handler

1 主线程定义Handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case message1:
//完成主界面更新,拿到数据
String data = (String)msg.obj;
textView.setText(data);
break;
default:
break;
}
}
};

2 开启一个子线程,完成耗时操作后,通知Handler完成UI更新:

1
2
3
4
5
6
7
8
9
10
new Thread(new Runnable(){
public void run(){
Thread.sleep(1000);//进行耗时操作后,发送消息给Handler,完成UI更新
handler.sentEmptyMessage(0);
//如果需要传输数据,可用下面的方法
Message msg = new Message();
msg.obj = "message1";
handler.sentMessage(msg);
}
}).start();

2 Activity对象的runOnUiThread方法更新

该方法的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

在子线程中通过runOnUiThread()方法来更新UI

1
2
3
4
5
6
7
8
new Thread(new Runnable(){
public void run(){
Thread.sleep(1000);//这里主要进行耗时操作
runOnUiThread(new Runnable(){
//更新UI
});
}
}).start();

这里需要注意的是runOnUiThread是Activity中的方法,在线程中我们需要告诉系统是哪个activity调用,所以前面显示的指明了activity。

1
2
3
4
5
6
7
8
Activity activity = (Activity) imageView.getContext();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});

这种方法使用比较灵活,但如果Thread定义在其他地方,需要传递Activity对象;

3 View.post(Runnable r)

一般在完成耗时操作后,可以用post方法来更新UI。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Handler mHandler;//全局变量
@Override
protected void onCreate(Bundle savedInstanceState) {
mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);//在子线程有一段耗时操作
mHandler.post(new Runnable() {
@Override
public void run() {
//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}

总结:UI的更新必须在主线程中完成,所以不管上述那种方法,都是将更新UI的消息发送到了主线程的消息对象,让主线程做处理。

Activity基础

发表于 2017-10-26

Android四大组件之一——Activity.

Ps 网上搜集

##1 Activity的生命周期

1 Activity生命周期

  • Activity主要的三种状态:

      Running(运行):在屏幕前台(位于当前任务堆栈的顶部)

      Paused(暂停):失去焦点但仍然对用户可见(覆盖Activity可能是透明或未完全遮挡)

      Stopped(停止):完全被另一个Activity覆盖

其生命周期示意图如下:

  • onCreate——创建Activity
    当Activity第一次创建的时候调用。这个方法里主要是提供给我们做一些初始化操作,如:创建view、绑定数据到view。onCreate在整个生命周期只会初始化一次外,他还有一个很重要的作用:当我们的Activity非正常销毁之后,例如手机旋转,内存不足导致的后台自动销销毁。为了保护我们的数据可以将数据保存在savedInstanceState中,当Activity重启数据依旧不会消失。我们可以通过onCreate方法中的savedInstance参数拿到我们的数据。

做法很简单只要重载onSaveInstanceState或者onRestoreInstance就可以了:

1
2
3
4
5
6
7
8
9
Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
  • onStart —Activity变成可见
    该方法的执行表示Activity已经可见了但是还无法和用户交互,只有当执行到onResume方法的时候才可以进行交互。如果activity开启了另一个不完全覆盖的activity B,而再次关闭这个B的时候,将不会执行这个方法,但是会执行onResume。也就是说,onstart在每次变得可见的时候才会执行一次。

  • onResume — Activity处于运行状态,获得用户焦点
    当用户从pause的状态resume到你的activity的时候,系统调用了onResume()的函数。onResume()会用来初始化在onPause()中释放的组件,并且执行一些其他在当activity进入resume状态的时候需要执行的初始化调用。此时Activity就会位于Activity栈的栈顶,并且可以与用户开始进行交互了。

  • onPause – 其他Activity获得用户焦点
    一般的在app使用中,前台的activity一般是会被视觉组件所遮住的,这就会导致activity的pause。一旦这个activity全部被遮住了,并且不可见,它就stop了。当你的activity收到了一个调用onPause()的请求,它可能表示这个activity将会被停止一段时间并且使用者很可能会再次回到你的activity来。但是这也很可能表示着用户正在离开你的app。在该方法中google给出的建议是存储一些重要的数据同时停止一些类似于动画等消耗CPU的工作。该方法的调用过程是很快的,否则会影响到后面的Activity的实现,所以在该方法里不宜做过多耗时操作。

  • onStop — Activity不再可见,处于停止状态
    Activity在不可见的时候,如被其他Activity完全覆盖,此Activity就处于onStop状态。此时如果Activity再次启用,调用onRestart,复活;如果Activity被销毁,调用onDestroy。销毁有两种原因:主动调用finish()或被系统回收。

  • onRestart
    onStop方法之后可能会调用到onRestart方法,这是因为代表的Activity正在被重新启动,然后紧接着就会继续走到onStart和onResume方法中。以下三种情况会调用:a.按下home键之后,然后切换回来,会调用onRestart();b.从本Activity跳转到另一个Activity之后,按back键返回原来Activity,会调用onRestart();c.从本Activity切换到其他的应用,然后再从其他应用切换回来,会调用onRestart();

  • onDestroy — Activity被销毁了
    在Activity的生命周期中,onDestory()方法是他生命的最后一步,资源空间等就被回收了。当重新进入此Activity的时候,必须重新创建,执行onCreate()方法。‍这里需要提到的一点是,即使一个Activity被销毁后app内部的static变量是不会被销毁的,因为static变量是全局的,activity销毁但是该app的进程并没有被杀死。所以说这一点尤为需要注意我们的static变量的使用,否则稍有不慎再次启动该activity的时候该static变量就会是一个dirty data!

2.生命周期分析

1).启动Activity->返回桌面->再次回到Activity
根据前面对生命周期的分析可以不难知道这三个过程Activity的生命周期方法调用顺序如下:
onCreat()——>onStart()——>onResume()
返回桌面则会调用:onPause()——>onStop()
再次回到Activity:onResart()——>onStart()——>onResume()

2).按back退出activity
此时会走onPause()——>onStop()——>onDestroy()方法
再次强调的是该方法即使退出了主Activity但是也没有杀掉进程,所以static变量并没有被销毁,再次进来的时候可能会是脏数据。就以我的经验碰到的最多的一个问题就是:有的时候会将一些名字什么的存在static变量里作为全局变量进行调用,此时测试人员的其中一个case会按back退出activity然后切换系统语言再次启动app。如果没有对static变量做一些销毁操作的话,再次回来就是一个dirty data,语言文字并没有切换导致了bug的存在。

3).在一个Activity中启动另一个Activity
从MainActivity中启动SecondActivity我们可以很清楚的看到MainActivity中的onPause方法执行完了以后然后新的SecondActivity的onCreate、onStart、onResume方法就会依次执行将SecondActivity显示出来,最后MainActivity的onStop方法才会被调用。这同时也验证了之前提到的,google并不建议在onPause方法里进行一些耗时操作,否则会影响SecondActivity的创建。

##2 Activity四种启动模式

###1 standard
默认模式,可以不用在AndroidManifest.xml里对应的标签设置android:launchMode属性。在这个模式下,每次创建Activity都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。
假设我有一个Activity名为A1, 上面有一个按钮可跳转到A1。那么如果我点击按钮,便会新启一个Activity A1叠在刚才的A1之上,再点击,又会再新启一个在它之上……
点back键会依照栈顺序依次退出。

###2 singleTop
可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。
假设有一个Activity为A1,其有一个按钮可以跳到A1.若我意图打开的顺序为A1->A1->A1,则实际上只会在第一次打开时创建A1(后两次意图打开A1,实际只调用了前一个的onNewIntent方法)

3 singleTask

只有一个实例。在同一个应用程序中启动它的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个Activity,singleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中。

若我的应用程序中有三个Activity,C1,C2,C3,三个Activity可互相启动,其中C2为singleTask模式,那么,无论我在这个程序中如何点击启动,如:C1->C2->C3->C2->C3->C1-C2,C1,C3可能存在多个实例,但是C2只会存在一个,并且这三个Activity都在同一个task里面。
但是C1->C2->C3->C2->C3->C1-C2,这样的操作过程实际应该是如下这样的,因为singleTask会把task中在其之上的其它Activity destory掉。
操作:C1->C2 C1->C2->C3 C1->C2->C3->C2 C1->C2->C3->C2->C3->C1 C1->C2->C3->C2->C3->C1-C2
实际:C1->C2 C1->C2->C3 C1->C2 C1->C2->C3->C1 C1->C2

若是别的应用程序打开C2,则会新启一个task。
如别的应用中有一个activity,taskId为200,从它打开C2,则C2的taskId不会为200,例如C2的taskId为201,那么再从C2打开C1、C3,则C1、C3的taskId仍为201。

4 singleInstance

只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。
例如:程序有三个ActivityD1,D2,D3,三个Activity可互相启动,其中D2为singleInstance模式。那么程序从D1开始运行,假设D1的taskId为200,那么从D1启动D2时,D2会新启动一个task,即D2与D1不在一个task中运行。假设D2的taskId为201,再从D2启动D3时,D3的taskId为200,也就是说它被压到了D1启动的任务栈中。
若是在别的应用程序打开D2,假设Other的taskId为200,打开D2,D2会新建一个task运行,假设它的taskId为201,那么如果这时再从D2启动D1或者D3,则又会再创建一个task,因此,若操作步骤为other->D2->D1,这过程就涉及到了3个task了。

3 Activity生命周期 onNewIntent

当我们在manifest文件里对Activity的launchmode进行设置为singleTop、singleTask、singleInstance的时候都会有可能调用到此方法.Activity第一启动的时候执行onCreate()—->onStart()等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent().如果不是复用之前的activity实例是不会调用onNewIntent)而后面如果再有想启动Activity的时候,那就是执行onNewIntent()—->onResart()——>onStart()—–>onResume(). 如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()—->onStart()—->onResume()等。
当调用到onNewIntent(intent)的时候,需要在onNewIntent() 中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。

匆匆——朱自清

发表于 2017-10-24

程序员日,来篇朱自清的《匆匆》吧!


燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?——是有人偷了他们罢:那是谁?又藏在何处呢?是他们自己逃走了罢——如今又到了哪里呢?

我不知道他们给了我多少日子,但我的手确乎是渐渐空虚了。在默默里算着,八千多日子已经从我手中溜去,像针尖上一滴水滴在大海里,我的日子滴在时间的流里,没有声音,也没有影子。我不禁头涔涔而泪潸潸了。

去的尽管去了,来的尽管来着;去来的中间,又怎样地匆匆呢?早上我起来的时候,小屋里射进两三方斜斜的太阳。太阳他有脚啊,轻轻悄悄地挪移了;我也茫茫然跟着旋转。于是——洗手的时候,日子从水盆里过去;吃饭的时候,日子从饭碗里过去;默默时,便从凝然的双眼前过去。我觉察他去的匆匆了,伸出手遮挽时,他又从遮挽着的手边过去,天黑时,我躺在床上,他便伶伶俐俐地从我身上跨过,从我脚边飞去了。等我睁开眼和太阳再见,这算又溜走了一日。我掩着面叹息。但是新来的日子的影儿又开始在叹息里闪过了。

在逃去如飞的日子里,在千门万户的世界里的我能做些什么呢?只有徘徊罢了,只有匆匆罢了;在八千多日的匆匆里,除徘徊外,又剩些什么呢?过去的日子如轻烟,被微风吹散了,如薄雾,被初阳蒸融了;我留着些什么痕迹呢?我何曾留着像游丝样的痕迹呢?我赤裸裸来到这世界,转眼间也将赤裸裸的回去罢?但不能平的,为什么偏要白白走这一遭啊?

你聪明的,告诉我,我们的日子为什么一去不复返呢?

handler

发表于 2017-10-19

1 handler

handler是Android给我们提供的一套更新UI的机制,也是一套消息处理机制。Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。通过创建一个Message对象,通过Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。

在下面介绍handler机制前,首先得了解以下几个概念:

  • Message,为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
  • Message Queue,用来存放通过Handler发布的消息,按照先进先出(FIFO)执行。
  • Handler是Message的主要处理者,在构建handler时候内部会跟Looper进行关联,通过Looper.myLooper()获取到Looper,找到Looper也就找到了MessageQueue。在handler中发送消息,其实就是向MessageQueue队列中发送消息。
  • Looper,扮演Message Queue和Handler之间桥梁的角色,每一个Looper内部都包含一个MessageQueue。Looper.loop()负责循环取出Message Queue里面的Message.然后Looper交付给相应的Handler进行处理。
  • UI thread 通常就是main thread,每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。

Android官方给出的一个最标准的异步消息处理线程的写法应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); // step 1
mHandler = new Handler() { // step 2
public void handleMessage(Message msg) { // step 3
// process incoming messages here
}
};
Looper.loop(); // step 4
}
}
  • step1:Looper.prepare()方法的源码如下:
1
2
3
4
5
6
7
8
9
10
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。Looper对象的构造函数源码如下:

1
2
3
4
5
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}

从源码中可以看出,每一个Looper对象都关联着一个MessageQueue 对象,MessageQueue对象主要管理Handler发送来的消息。
以上可以看出每个线程中最多只会有一个Looper对象和一个MessageQueue对象。

  • step2: Handler的构造方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

从上面的源码可知在构造handler对象的时候,其内部会关联一个Looper对象。

  • step3 handleMessage方法主要处理MessageQueue中Message。调用sendMessage方法最后都会辗转到对sendMessageAtTime的调用,其源码如下:
1
2
3
4
5
6
7
8
9
10
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

在sendMessageAtTime主要是将msg信息enqueue进队列中。

  • step 4 Looper.loop()源码如下:
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
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}

可以看到,这个方法会进入了一个死循环,然后不断地调用的MessageQueue的next()方法。如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。loop方法会调用 msg.target.dispatchMessage(msg)。看一下dispatchMessage方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void dispatchMessage(Message msg) {
if (msg.callback != null) {// Message中callback 是一个Runnable对象
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
ps:
private static void handleCallback(Message message) {
message.callback.run();
} // 如果msg为一个Runnable对象,则会直接调用Runnable的Run方法

如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。


小结:Handler负责发送和处理Message,MessageQueue负责管理Message。Looper负责监视MessageQueue状态并从Queue中取出Message交由Handler来处理它。


12
大侠先锋

大侠先锋

15 日志
8 标签
GitHub ZhiHu
© 2017 大侠先锋
由 Hexo 强力驱动
主题 - NexT.Pisces
感谢小清妹妹的支持
访问量 总访问量次