设计模式中类的关系

在Java以及其他的面向对象设计模式中,类与类之间主要有6种关系,他们分别是:依赖、关联、聚合、组合、继承、实现。他们的耦合度依次增强。

  • 依赖(Dependence)

    依赖关系的定义为:对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。定义比较晦涩难懂,但在java中的表现还是比较直观的:类A当中使用了类B,其中类B是作为类A的方法参数、方法中的局部变量、或者静态方法调用。类上面的图例中:People类依赖于Book类和Food类,Book类和Food类是作为类中方法的参数形式出现在People类中的。

代码样例:

1
2
3
4
5
6
7
8
9
public class People {

public void read(Book book){

System.out.println("读的书是"+book.getName());

}

}
  • 关联(Association)
    单项关联:

    双向关联:

对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系。关联关系分为单向关联和双向关联。在java中,单向关联表现为:类A当中使用了类B,其中类B是作为类A的成员变量。双向关联表现为:类A当中使用了类B作为成员变量;同时类B中也使用了类A作为成员变量。

代码样例:

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

Father father = new Father();

System.out.println("从"+father.getName()+"获得礼物");

}

public class Father {

Son son = new Son();

System.out.println("从"+son.getName()+"获得礼物")

}
  • 聚合(Aggregation)

聚合关系是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的,而聚合关系的对象之间存在着包容关系,他们之间是“整体-个体”的相互关系。

代码样例:

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 People{

Car car;

House house;

public void setCar(Car car){

this.car = car;

}

public void setHouse(House house){

this.house = house;

}

public void driver(){

System.out.println("车型号:"+car.getType());

}

public void sleep(){

System.out.println("我在房子里睡觉:"+house.getAddress());

}

}

  • 组合(Composition)

相比于聚合,组合是一种耦合度更强的关联关系。存在组合关系的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。在下图的例子中,People与Soul、Body之间是组合关系,当人的生命周期开始时,必须同时有灵魂和肉体;当人的生命周期结束时,灵魂肉体随之消亡;无论是灵魂还是肉体,都不能单独存在,他们必须作为人的组成部分存在。

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
public class People{

Soul soul;

Body body;

public People(Soul soul,Body body){

this.soul = soul;

this.body = body;

}

public void study(){

System.out.println("学习要灵魂:"+soul.getName());

}

public void eat(){

System.out.println("吃饭需要身体:"+body.getName());

}

}
  • 继承(GeneraliZation)

继承表示类与类(或者接口与接口)之间的父子关系。在java中,用关键字extends表示继承关系。UML图例中,继承关系用实线+空心箭头表示,箭头指向父类。

  • 实现(Implementation)

表示一个类实现一个或多个接口的方法。接口定义好操作的集合,由实现类去完成接口的具体操作。在java中使用implements表示。UML图例中,实现关系用虚线+空心箭头表示,箭头指向接口。

service详解

android server 详解

Service的种类:

一、Service的种类

  1. 本地服务, Local Service
    用于应用程序内部。在Service可以调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf()

    Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次stopService()来停止。
  2. 远程服务, Remote Service
    用于android系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()方法建立连接,并启动,以调用
    Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

提供给可被其他应用复用,比如定义一个天气预报服务,提供与其他应用调用即可。

二、生命周期

1). 被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。

2). 被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。

3).被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。

4).当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。

特别注意:

  1. 你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService
    解除绑定(尽管Activity被finish的时候绑定会自动解除,并且Service会自动停止)

  2. 你应当注意 使用 startService 启动服务之后,一定要使用
    stopService停止服务,不管你是否使用bindService。

  3. 同时使用 startService 与 bindService 要注意到,Service
    的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与
    bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService
    之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用
    bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止。

  4. 当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity
    的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同

  5. 在 sdk 2.0 及其以后的版本中,对应的 onStart 已经被否决变为了 onStartCommand,不过之前的 onStart
    任然有效。这意味着,如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本,那么你应当使用 onStartCommand 而不是
    onStart。
    此处输入图片的描述

例子:

    public class ServiceDemo extends Service {

    public static final String TAG = "ServiceDemo" ;
    public static final String ACTION = "com.demo.SERVICE_DEMO";

    /**
     * onBind 是 Service 的虚方法,因此我们不得不实现它。
     * 返回 null,表示客服端不能建立到此服务的连接,所以不会调用onServiceConnected。
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, this.toString() + " ServiceDemo onBind");
        return null;
    }

    @Override
    public void onCreate() {
        Log.i(TAG, this.toString() + " ServiceDemo onCreate");
        super.onCreate();
    }

    @Override
    public void onStart(Intent intent, int startId) {
    Log.i(TAG, this.toString() + " ServiceDemo onStart");
    super.onStart(intent, startId);
    }

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

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, this.toString() + " ServiceDemo onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, this.toString() + " ServiceDemo onDestroy");
        super.onDestroy();
    }

}
<!-- android:exported 这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。
     如果设置为true,则能够被调用或交互,否则不能。设置为false时,
     只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。-->
<service android:name=".ServiceDemo" android:exported="false">
    <intent-filter>
        <action android:name="com.demo.SERVICE_DEMO" />
        <category android:name="android.intent.category.default" />
    </intent-filter>
</service>

通过Context.startService(Intent)方法启动service或者Context.bindService方法来绑定service


    public class MainActivity extends ActionBarActivity {

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

        findViewById(R.id.btn_bindService).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
            bindService(new Intent(ServiceDemo.ACTION), conn, BIND_AUTO_CREATE);
        }
    });

        findViewById(R.id.btn_unbindService).setOnClickListener(new OnClickListener() {
            @Override
        public void onClick(View v) {
        unbindService(conn);
        }
    });

        findViewById(R.id.btn_startService).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
            startService(new Intent(ServiceDemo.ACTION));
            }
        });

        findViewById(R.id.btn_stopService).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
        stopService(new Intent(ServiceDemo.ACTION));
        }
    });
    }

    ServiceConnection conn = new ServiceConnection() {
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(ServiceDemo.TAG, service.toString() + " onServiceConnected");
        }
    public void onServiceDisconnected(ComponentName name) {
        Log.i(ServiceDemo.TAG, "onServiceDisconnected");
    }
    };

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

此处输入图片的描述

日志输出:

此处输入图片的描述
上面的截图是点击绑定服务时输出的。可以看出,只调用了onCreate方法和onBind方法,当重复点击绑定服务时,没有再输出任何日志,并且不报错。onCreate方法是在第一次创建Service时调用的,而且只调用一次。另外,在绑定服务时,给定了参数BIND_AUTO_CREATE,即当服务不存在时,自动创建,如果服务已经启动了或者创建了,那么只会掉调用onBind方法。
此处输入图片的描述
当解除绑定的时,可以看出,Service调用onUnbind和onDestroy销毁了服务。
此处输入图片的描述
上面的截图是在多次点击启动服务时输出的。可以看出,在第一次点击时,因为Service还未创建,所以调用了onCreate方法,紧接着调用了onStartCommand和onStart方法。当再次点击启动服务时,仍然调用了onStartCommand和onStart方法,所以,在Service中做任务处理时需要注意这点,因为一个Service可以被重复启动。
此处输入图片的描述
当点停止服务的时,Service只是执行onDestroy方法,跟绑定还是有点小区别。
这里说一下,平常使用多的是startService方法,可以把一些耗时的任务放到后台去处理,当处理完成后,可以通过广播来通知前台。
而onBind方法更多的是结合AIDL来使用,这样一个应用可以通过绑定服务获得的IBinder来拿到后台的接口,进而调用AIDL中定义的方法,进行数据交换等。

三、Local 与 Remote 服务绑定

1) Local 服务绑定:Local 服务的绑定较简单,首先在 Service 中我们需要实现 Service 的抽象方法 onBind,并返回一个实现 IBinder 接口的对象。

Service 中的代码:

    public class LocalService extends Service{

    public static final String TAG = "LocalService" ;
    public static final String ACTION = "com.demo.LOCAL_SERVICE";
    public SimpleBinder sBinder;

    public class SimpleBinder extends Binder{

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

        public int add(int a, int b){
            return a + b;
        }
    }

    @Override
    public void onCreate() {
    super.onCreate();
    // 创建 SimpleBinder
        sBinder = new SimpleBinder();
    }

    @Override
    public IBinder onBind(Intent intent) {
    // 返回 SimpleBinder 对象
    Log.i(TAG, "LocalService onBind");
        return sBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
    Log.i(TAG, "LocalService onUnbind");
    return super.onUnbind(intent);
    }

}
<service android:name=".LocalService" android:exported="false">
    <intent-filter>
    <action android:name="com.demo.LOCAL_SERVICE" />
    <category android:name="android.intent.category.default" />
    </intent-filter>
</service>
public class LocalActivity extends ActionBarActivity {

    private ServiceConnection sc;
    private boolean isBind;

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

    sc = new ServiceConnection() {

            @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(LocalService.TAG, "onServiceConnected");
        LocalService.SimpleBinder sBinder = (LocalService.SimpleBinder)service;
                Log.i(LocalService.TAG, "3 + 5 = " + sBinder.add(3, 5));
        }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(LocalService.TAG, " onServiceDisconnected");
            }
        };

        findViewById(R.id.btn_bindService).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                bindService(new Intent(LocalService.ACTION), sc, Context.BIND_AUTO_CREATE);
                isBind = true;
            }
        });
        findViewById(R.id.btn_unbindService).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if(isBind){
                    unbindService(sc);
                    isBind = false;
                }
            }
        });
    }
}

在 Activity 中,我们通过 ServiceConnection 接口来取得建立连接 与 连接意外丢失的回调。bindService有三个参数,第一个是用于区分 Service 的Intent 与 startService 中的 Intent 一致,第二个是实现了 ServiceConnection 接口的对象,最后一个是 flag 标志位。有两个flag,BIND_DEBUG_UNBIND 与 BIND_AUTO_CREATE,前者用于调试(详细内容可以查看javadoc 上面描述的很清楚),后者默认使用。unbindService 解除绑定,参数则为之前创建的 ServiceConnection 接口对象。另外,多次调用 unbindService 来释放相同的连接会抛出异常,因此我创建了一个 boolean 变量来判断是否 unbindService 已经被调用过。

运行结果:
此处输入图片的描述

在绑定服务的时候,需要一个服务连接对象,ServiceConnection,服务一旦连接,就会调用onServiceConnected方法,我们可以在这个方法里面返回我们的本地服务对象,具体看代码;而在服务断开时候会调用onServiceDisconnected方法,我们可以清理一些服务资源。

2) Remote 服务绑定:之前所谈的Service属于Local Service,即Service和Client在同一进程内(即同一application内),Service的生命周期服从进程的生命周期。在实际应用上,有时希望Service作为后台服务,不仅被同一进程内的activity使用,也可被其他进程所使用。

通常每个应用程序都在它自己的进程内运行,但有时需要在进程之间传递对象(IPC通信),你可以通过应用程序UI的方式写个运行在一个不同的进程中的service。在android平台中,一个进程通常不能访问其它进程中的内存区域。所以,他们需要把对象拆分成操作系统能理解的简单形式,以便伪装成对象跨越边界访问。编写这种伪装代码相当的枯燥乏味,好在android为我们提供了AIDL工具可以来做这件事。

AIDL(android接口描述语言)是一个IDL语言,它可以生成一段代码,可以使在一个android设备上运行的两个进程使用内部通信进程进行交互。如果你需要在一个进程中(例如在一个Activity中)访问另一个进程中(例如一个Service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。

此处输入图片的描述
Android提供AIDL(Android Interface Definition Language)工具帮助IPC之间接口的建立,大大地简化了开发者视图。通过下面的步骤实现client和service之间的通信:
1)定义AIDL接口 ,Eclipse将自动为Service建立接口IService
2)Client连接Service,连接到IService暴露给Client的Stub,获得stub对象;换句话,Service通过接口中的Stub向client提供服务,在IService中对抽象IService.Stub具体实现。
3)Client和Service连接后,Client可向使用本地方法那样,简单地直接调用IService.Stub里面的方法。
下面的例子给出client从提供定时计数的Remote Service,称为TestRemoteService,中获得服务的例子。

步骤1:通过AIDL文件定义Service向client提供的接口,ITestRemoteService.aidl文件如下

interface ITestRemoteService {
    int getCounter();
}

我们在src的目录下添加一个ITestRemoteService.aidl文件,语法和java的相同。在这个例子中Service很简单,只提供计数器的值,故在接口中我们定义了int getCounter( )。

AIDL文件很简单,Eclipse会根据文件自动生成相关的一个java interface文件,不过没有显示出来,如果直接使用命令行工具会帮助生成java文件。

步骤2:Remote Service的编写,通过onBind(),在client连接时,传递stub对象。 TestRemoteService.java文件如下

//Service提供一个定时计数器,采用Runnable的方式实现。
public class TestRemoteService extends Service{
    private Handler serviceHandler = null;
    private int counter = 0;
    private TestCounterTask myTask = new TestCounterTask();

    public void onCreate() {
        super.onCreate();
        showInfo("remote service onCreate()");
    }

    public void onDestroy() {
        super.onDestroy();
        serviceHandler.removeCallbacks(myTask); //停止计数器
        serviceHandler = null;
        showInfo("remote service onDestroy()");
    }

    public void onStart(Intent intent, int startId) {
       // 开启计数器
        super.onStart(intent, startId);
        serviceHandler=new Handler();
        serviceHandler.postDelayed(myTask, 1000);
        showInfo("remote service onStart()");
    }

   //步骤2.1:具体实现接口中暴露给client的Stub,提供一个stub inner class来具体实现。
    private ITestRemoteService.Stub stub= new ITestRemoteService.Stub() {
       //步骤2.1:具体实现AIDL文件中接口的定义的各个方法。
        public int getCounter() throws RemoteException {
            showInfo("getCounter()");
            return counter;
        }
    };

//步骤2.2:当client连接时,将触发onBind(),Service向client返回一个stub对象,
//由此client可以通过stub对象来访问Service,本例中通过stub.getCounter()就可以获得计时器的当前计数。
//在这个例子中,我们向所有的client传递同一stub对象。
   public IBinder onBind(Intent arg0) {
   //我们特别跟踪了stub对象的地址,可以在client连接service中看看通过ServiceConnection传递给client
        showInfo("onBind() " + stub);
        return stub;
   }

    //用Runnable使用定时计数器,每10秒计数器加1。
    private class TestCounterTask implements Runnable{
        public void run() {
            ++ counter;
            serviceHandler.postDelayed(myTask,10000);
            showInfo("running " + counter);
        }
    }
    //showInfo() 帮助我们进行信息跟踪,更好了解Service的运行情况
    private void showInfo(String s){
        System.out.println("[" +getClass().getSimpleName()+"@" + Thread.currentThread().getName()+ "] " + s);
    }
}

此处输入图片的描述

步骤3:Client和Service建立连接,获得stub,ServiceTest4.java代码如下

public class ServiceTest4 extends Activity{

    //步骤3.1 定义接口变量
    private ITestRemoteService remoteService = null;
    private boolean isStarted = false;
    //步骤3.1 定义连接变量,实现ServiceConnection接口
    private CounterServiceConnection conn = null;

    protected void onCreate(Bundle savedInstanceState) {
        //5个button分别触发startService( ),stopService( ) ,
        //bindService( ), releaseService( )和invokeService( ),
        //下面两行,一行是显示从Service中获得的计数值,一行显示状态。
    }

    private void startService(){
        Intent i = new Intent();
        //我的这个包里面还有层次,如*.part1、*.part2,etc
        i.setClassName("com.wei.android.learning", "com.wei.android.learning.part5.TestRemoteService");
//和之前的local service一样,通过intent开启Service,触发onCreate()[if Service没有开启]->onStart()
        startService(i);
        isStarted = true;
        updateServiceStatus();
    }
    private void stopService(){
        Intent i = new Intent();
        i.setClassName("com.wei.android.learning","com.wei.android.learning.part5.TestRemoteService");
        stopService(i); //触发Service的 onDestroy()[if Service存在]
        isStarted = false;
        updateServiceStatus();
    }
   //步骤3.3:bindService( )通过一个实现ServiceConnection接口的类于Service之间建立连接,
   //注意到里面的参数Context.BIND_AUTO_CREATE,触发onCreate()[if Service不存在] –> onBind().
    private void bindService(){
        if(conn == null){
            conn = new CounterServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.wei.android.learning","com.wei.android.learning.part5.TestRemoteService");
           bindService(i, conn,Context.BIND_AUTO_CREATE);
            updateServiceStatus();
        }
    }

    private void releaseService(){
        if(conn !=null){
            unbindService(conn); //断开连接,解除绑定
            conn = null;
            updateServiceStatus();
        }
    }
    private void invokeService(){
        if(conn != null){
            try{
            //一旦client成功绑定到Service,就可以直接使用stub中的方法。
                Integer counter =remoteService.getCounter();
                TextView t = (TextView)findViewById(R.id.st4_notApplicable);
                t.setText("Counter value : " + Integer.toString(counter));
            }catch(RemoteException e){
                Log.e(getClass().getSimpleName(),e.toString());
            }
        }
    }
    //步骤3.2 class CounterServiceConnection实现ServiceConnection接口,
    //需要具体实现里面两个触发onServiceConnected()和onServiceDisconnected()
    private class CounterServiceConnection implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 从连接中获得stub对象,根据我们的跟踪,remoteService就是service中的stub对象
            remoteService = ITestRemoteService.Stub.asInterface(service);
            showInfo("onServiceConnected()" + remoteService);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            remoteService = null;
            updateServiceStatus();
            showInfo("onServiceDisconnected");
        }
    }

    private void updateServiceStatus() {
        TextView t = (TextView)findViewById( R.id.st4_serviceStatus);
        t.setText( "Service status: "+(conn == null ? "unbound" : "bound")+ ","+ (isStarted ? "started" : "not started"; ));
      }

    private void showInfo(String s){
        System.out.println("[" +getClass().getSimpleName()+"@" + Thread.currentThread().getName()+ "] " + s);
    }
}

此处输入图片的描述

注意:

Service.onBind如果返回null,则调用 bindService 会启动 Service,但不会连接上 Service,因此 ServiceConnection.onServiceConnected 不会被调用,但你任然需要使用 unbindService 函数断开它,这样 Service 才会停止。

其它:

1、在什么情况下使用 startService 或 bindService 或 同时使用startService 和 bindService

如果你只是想要启动一个后台服务长期进行某项任务那么使用 startService 便可以了。如果你想要与正在运行的 Service 取得联系,那么有两种方法,一种是使用 broadcast ,另外是使用 bindService ,前者的缺点是如果交流较为频繁,容易造成性能上的问题,并且 BroadcastReceiver 本身执行代码的时间是很短的(也许执行到一半,后面的代码便不会执行),而后者则没有这些问题,因此我们肯定选择使用 bindService(这个时候你便同时在使用 startService 和 bindService 了,这在 Activity 中更新 Service 的某些运行状态是相当有用的)。另外如果你的服务只是公开一个远程接口,供连接上的客服端(android 的 Service 是C/S架构)远程调用执行方法。这个时候你可以不让服务一开始就运行,而只用 bindService ,这样在第一次 bindService 的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是Remote Service,那么该效果会越明显(当然在 Service 创建的时候会花去一定时间,你应当注意到这点)。

2、在 AndroidManifest.xml 里 Service 元素的常见选项

android:name,服务类名

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

android:icon,服务的图标

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

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

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

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

3、Service 与 Thread 的区别

很多时候,你可能会问,为什么要用 Service,而不用 Thread 呢,因为用 Thread 是很方便的,比起 Service 也方便多了,下面我详细的来解释一下。

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

2). 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 做不到的。

4、拥有service的进程具有较高的优先级

官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。

1). 如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。

2). 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.

3). 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。

4). 如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。

如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

5、注意事项

Service的onCreate的方法只会被调用一次,就是你无论多少次的startService又 bindService,Service只被创建一次。如果先是bind了,那么start的时候就直接运行Service的onStart方法,如果先是start,那么bind的时候就直接运行onBind方法。如果你先bind上了,就stop不掉了,只能先UnbindService, 再StopService,所以是先start还是先bind行为是有区别的。

Android中的服务和windows中的服务是类似的东西,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的 程序。

服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。

这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同 时生,必须同时死”的特点。

如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次 创建服务,但会导致多次调用onStart()方法。采用startService()方法启动的服务,只能调用Context.stopService()方法结 束服务,服务结束时会调用onDestroy()方法。

如果打算采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法。这个时候调用者和服务绑定在一起,调用者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。

如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的 onUnbind()–>onDestroy()方法.

原文地址:http://aswang.iteye.com/blog/1424309

淘汰

陈奕迅 - 淘汰
曲 : 周杰伦 词:周杰伦

我说了 所有的谎
你全都相信
简单的 我爱你
你却老不信
你书里的剧情
我不想上演
因为我喜欢 喜剧收尾

我试过 完美放弃
的确很踏实
醒来了
梦散了
你我都走散了
情歌歌词何必押韵
就算我是K歌之王
也不见得把 爱情唱得完美

只能说我输了
也许是你怕了
我们的回忆 没有皱褶
你却用离开烫下句点
只能说我认了
你的不安赢得你信任
我却得到你 安慰的淘汰

我试过完美放弃
的确很踏实
醒来了
梦散了
你我都走散了
情歌歌词何必押韵
就算我是K歌之王
也不见得把 爱情唱得完美

只能说我输了
也许是你怕了
我们的回忆 没有皱褶
你却用离开烫下句点
只能说我认了
你的不安赢得你信任
我却得到你 安慰的淘汰

只能说我输了
也许是你怕了
我们的回忆 没有皱褶
你却用离开烫下句点
只能说我认了
你的不安赢得你信任
我却得到你 安慰的淘汰

offce 001

View的绘制流程、Activity、Window、View的关系
Activity启动时创建Window、ViewRoot并建立关联,流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ActivityThread {
// startActivity最终会调用到这里
fun handleLaunchActivity(){
// 1. performLaunchActivity() 创建activity
// 2. activity.attach() 内部创建了PhoneWindow
// 3. activity.onCreate() -> setContentView,实际调用window的对应方法,创建DecorView
}

fun handleResumeActivity(){
// 1. activity.onResume()
// 2. 获取activity的window对象,添加DecorView到WindowManagerGlobal中
// 3. WindowManagerGlobal.addView(DecorView)时,创建了ViewRootImpl,所有view绘制的工作都是
// ViewRootImpl来调度,在这里才建立了ViewRootImpl和View的关联
}
}

class ViewRoot {
// ViewRoot添加view后,会执行ViewRoot.requestLayout
// scheduleTraversals -> 在消息队列中插入一个同步消息屏障,保证UI优先绘制
// -> 通过choreographer提交绘制任务,同时向底层请求sync信号,
// -> 在下一次信号到来时JNI回调doTraversal,并移除屏障消息
// -> doTraversal中调用了performMeasure、performLayout和performDraw进行测量、布局和绘制流程
}

MeasureSpec是一个32位int值,高2位表示测量模式,后30位表示在该模式下的测量值,一个view的MeasureSpec由自己的LayoutParams和父View的MeasureSpec共同决定。测量过程实际是递归的测量子view后再设置自己的尺寸。

onCreate、onResume中能否获取到View的宽高,为什么?

由于View的measure过程和Activity的生命周期方法不是同步执行的,如果View还没有测量完毕,那么获得的宽/高就是0。所以在onCreate、onStart、onResume中均无法正确得到某个View的宽高信息。
解决方式如下:

1.view.post(runnable),注意这里handler.post(runnable)是不行的,View.post会先判断attachInfo是否为空,如果为空就放到一个等待执行的队列中,等待View被添加(dispatchAttachedToWindow)之后才执行,这时测量已经完毕了,如果不为空,表示View已经被添加,就调用attachInfo中的Handler来post任务,所以是一定能获取到的。注意api23以下和以上的逻辑不一样:

Api23以下:调用的ViewRootImpl.getRunQueue().post(),执行时机是doTraversa()中,这个方法又是在下一个同步信号来的时候调用的,参考屏幕刷新机制。

Api23以上:调用的是getRunQueue().post() ,它的执行时机是View被添加之后执行,如果View只是创建出来没有被添加,那将一直得不到执行。

2.使用ViewTreeObserver

卡顿原理、屏幕刷新机制、卡顿监控

卡顿的根本原因:view在16ms内不能完成绘制造成掉帧,常见造成掉帧的原因又有:

1.主线程有耗时操作(合理使用线程来执行耗时任务)
2.View本身太复杂、嵌套过多导致绘制超过16ms(优化View的层级、合理使用include、ViewStub标签)
3.内存抖动造成频繁GC,例如循环内部创建对象,onDraw中创建对象等。(优化内存泄漏、对一些需要频繁创建的对象采用对象池技术)

屏幕刷新机制:

1.基于handler消息队列,如BlockCanary,handler分发消息前后都会打印日志,可以自定义Printer,计算Looper两次获取消息的时间差,如果时间太长就说明Handler处理时间过长,直接把堆栈信息打印出来,就可以定位到耗时代码
2.代码插桩,在方法的前后插入计时代码来监控执行时间,缺点是包增大,无法监控系统方法,并且需要过滤简单方法
3.循环插入空消息到消息队列,监控这个消息的处理时间,例如每隔1秒插入一条空消息,如果这条消息处理时间间隔大于一定时间,则认为发生了卡顿

RxJava的原理

Rxjava每个操作符会生成一个新的Observable,同时持有上游事件源和下游Observer,最终在subscribeActual中实现自己的操作逻辑,并连接上下游。
Rxjava有点像观察者模式和责任链模式的结合,普通的观察者模式一般是被观察者通知多个观察者,而Rxjava则是被观察者通知第一个Obsever,接下来Observer依次通知下一个节点的Observer,形成一个“观察链”,将观察者模式进行了一种类似链式的变换,每个节点又会执行它不同的“职责”。

1.SubscribeOn节点在订阅的时候,将它的上游节点的subscribe操作,以runnable的形式交给调度器在执行,在io调度器就是一个线程池,他影响的是事件源的发射行为,如果多次subscribeOn相当后一次subscribeOn把上一次subscribeOn行为在线程池里执行了一次,最终就只有最上边的一个起作用;
2.observeOn会将它下游的Observer放到切换的线程中执行,因此observeOn影响的是它的下游,多次调用影响的是这次到下一次observeOn之间的代码;

Rxjava中调度器

Schedulers.io():无边界线程池作为支撑的一个Scheduler,线程可以无限增长,它适用于非CPU密集的I/O工作,比如访问文件系统、执行网络调用、访问数据库等
Schedulers.computation():用于执行CPU密集的工作,比如处理大规模的数据集、图像处理等等。它由一个有界的线程池作为支撑,线程的最大数量就是可用的处理器数量
Schedulers.newThread():这个Scheduler 每次都会创建一个全新的线程来完成一组工作
Schedulers.single():只有一个线程作为支撑,只能按照有序的方式执行任务
Schedulers.from(Executor executor)我们可以使用它创建自定义的Scheduler
AndroidSchedulers.mainThread():Android主线程调度器

事件分发机制

事件传递的顺序:Activity->Window->DecorView

dispatchTouchEvent中:

1.判断是否需要拦截事件的标记intercepted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 如果是down事件或者mFirstTouchTarget不为空
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 判断disallowIntercept标记
// 如果允许拦截则调用onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
} else {
// 有disallowIntercept标记,不拦截
intercepted = false;
}
} else {
// 已有mFirstTouchTarget或者不是down时间,直接拦截
intercepted = true;
}

2.尝试分发事件:如果第一步不需要拦截,并且不是cancel状态,分发给子view

1
2
3
4
5
if (!canceled && !intercepted) {
// 遍历子view,判断坐标是否在view范围内并且view没有处于动画状态
// 满足条件则交给子view的dispatchTouchEvent来处理
// 如果子view处理了事件,则把子view赋值给mFirstTouchTarget
}

3.再次分发事件:判断firstTouchTarget是否为空,如果为空表示没有子view处理事件,则间接的交给自己的onTouchEvent来处理,为空则直接交给firstTouchTarget处理

1
2
3
4
5
if (mFirstTouchTarget == null) {
// 没有子view处理事件,交给自己的onTouchEvent处理
} else {
// 直接交给mFirstTouchTarget处理后续事件
}

4.onTouchListener :如果有onTouchListener则优先交给onTouchListener处理,onTouchListener返回true则onTouchEvent将不会调用
5.cancel事件:父视图先不拦截,然后在 MOVE 事件中重新拦截,此时子 View 会接收到一个 CANCEL 事件,例如move出子view范围,或者scrollView中事件首先传递给子view,如果滑动则会被拦截。

LeakCanary原理

首先java的四种引用,强、软、弱、虚四种引用,配合ReferenceQueue使用,在构造弱引用时传入ReferenceQueue,在垃圾回收之前,会将引用放入队列中,可以通过队列中是否有对象的引用来判断对象是否被回收;

具体就是在Application中注册ActivityLifecycleCallbacks监听activity的生命周期,在onDestory的时候,新建一个弱引用传入队列,在线程空闲的时候,会尝试清除队列的弱引用,如果成功则没有发生泄漏,如果失败,则尝试GC,GC之后再
次尝试清除弱引用,如果失败则发生了内存泄漏

Fragment也类似,在Activity创建时获取到FragmentManager注册一个fragmentLifecycleCallbacks,然后观察fragment;

Retrofit原理

核心原理:在createService时,创建一个Api接口的动态代理,在loadServiceMethod方法先查找缓存,没有找到则解析Api接口,解析包括两部分,一个是方法上的注解,包括url、Header、请求参数等,第二个是方法的返回类型和参数类型,也就是CallAdapter和ConverAdapter

CallAdapter用于把结果适配成Rxjava、kotlin协程等返回类型

ConverAdapter用于把参数转化为json或者其他格式传输。

loadServiceMethod完毕后,实际上是把请求组装成一个OkHttpCall,用okhttp来进行具体的请求。

OkHttp原理

优点:连接池技术复用连接,可以降低延迟,无缝支持gzip减少数据量,支持http2以及SPDY多路复用技术

责任链设计模式:用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕

请求流程:

1.构建request
2.通过dispatcher执行请求,dispatcher内部包含三个队列:同步请求队列、异步等待队列和异步执行队列,对于同步请求,直接添加到同步请求队列执行,异步请求则添加到等待队列中,然后判断请求数是否大于最大请求数、以及同一个主机的最大请求数,如果可以执行,则提升到执行队列进行执行
3.无论是同步请求还是异步请求,最终都调用到getResponseWithInterceptorChain方法,核心就是拦截器链,包含了7大拦截器,分别负责不同的功能,每一个拦截器都可以自己处理请求,然后直接返回或者交给下一节点来处理。

自定义拦截器
RetryAndFollowUpInterceptor:重试和重定向拦截器
BridgeInterceptor:用来设置一些必要的header
CacheInterceptor:处理缓存
ConnectInterceptor:负责建立服务器连接,优先从连接池中找到可用连接(soket连接是否可用,是否超时等,如不可用则会从连接池中移除),否则打开一个新的连接
自定义网络拦截器
CallServerInterceptor:最后一个拦截器,用于真正发送网络请求,返回response

为什么要组件化,怎么实现?了解的路由框架

痛点:在业务开发中,各个业务模块依赖关系复杂,耦合严重,造成app编译缓慢、不能并行开发、组件复用性不高的问题。通过组件化改造,可以对各个模块进行了业务隔离,使模块可以单独编译运行,降低耦合度,提高了开发和调试效率。

组件拆分:

应用入口:一个空壳app,提供应用的启动页面

支撑业务组件:网络请求、图片加载等一些支撑服务

通用业务模块:例如支付、推送、地图等sdk,封装后对外提供调用接口

应用业务组件:具体的业务,可以提出来作为一个单独应用的功能模块,两两之间不互相依赖

模块间的通信,需要引入路由框架,主流路由框架大概分为两种

ARouter:路由表+接口下沉的方式,在编译期间通过注解处理器扫描目标类,然后解析参数生成路由表,
模块服务通过接口下沉的方式,把接口定义在公共库中,具体实现类在业务模块中,在编译时通过扫描字节码发现实现类,然后通过ASM等字节码修改技术完成服务的注册;

CC:组件总线的方式,类似于EventBus发送消息一样,直接找到目标组件,发送一条指令执行对应操作,
通过gradle的Transform API:在编译时(dex/proguard之前)扫描当前要打包到apk中的所有类 ,然后通过ASM技术修改字节码,生成代码完成组件的注册;

android优化的数据结构:SparseArray、ArrayMap

SparseArray的key只能为int,双列数组结构,优化了自动装箱的过程,并且会自动排序,查找采用二分查找,在数据量不大的情况下效率高

ArrayMap和SparseArray类似,但是key可以是对象,存储的时候存储通过key的hash值排序,也采用二分查找;

进程间通信的方式

1.广播

2.Messenger,封装的Binder,可以执行简单的串行通信。

3.ContentProvider,适用于进程间的数据共享,可以进行操作权限的控制,如数据库的读写等。

4.Socket,适用于网络通信,用于传输原始的字节流,两次拷贝、效率低于binder

5.Binder,适用于复杂的进程通信,功能强大,CS架构,底层基于内存映射,只进行一次拷贝,所以效率高,可以验证通信的进程id,所以比socket更安全。

AIDL和Binder机制

AIDL实际上是一套快速实现Binder通信的工具,通过定义AIDL接口,系统会自动生成一个类实现IInterface接口,内部包含Stub和Proxy两个内部类。服务端继承Stub提供服务。

关键类和方法

Stub:服务端通过继承Stub,实现定义的接口来提供服务

Proxy:服务端对象的本地代理,客户端通过它来间接调用服务端接口的方法

asInterface:客户端调用,将binder对象转化为AIDL接口类型对象,如果和服务端在同一进程,返回Stub对象本身,否则返回Stub.proxy代理对象

asBinder:返回binder对象

onTransact:运行在服务端binder线程池,客户端请求时,远程请求会被系统封装后交给此方法处理

transact:运行在客户端,客户端发起请求时线程挂起,调用到服务端onTransact,等待其返回后才继续执行

Binder机制

基于C/S架构,包括Server、Client、ServiceManager和Binder驱动,其中Binder驱动运行在内核空间,其他的运行在用户空间

ServiceManager负责服务管理,服务端向ServiceManager注册后,客户端可以向ServiceManager查询获取到目标服务的引用

Binder驱动:负责进程之间的Binder通信的建立,基于内存映射技术,传统的通信方式需要将数据从用户空间拷贝到内核空间,然后从内核空间拷贝到目标进程的用户空间,需要两次拷贝,Binder驱动可以建立用户空间内存和内核空间内存的一个映射,

只需要一次拷贝,非常高效。

kotlin的扩展方法是怎么实现的,inline关键字的作用

扩展方法编译成java后,实际上是一个 public static的静态方法,传入了对象实例和参数
Inline关键字修饰的函数是内联函数,用于优化函数调用的压栈操作,相当于在编译期间把内联函数的代码拷贝到函数调用处,可以提升性能,但是增加了代码量

Handler机制

关键类:

Handler:用于发送消息和处理消息,发送消息就是将消息对象放入当前线程的MessageQueue中,然后Looper轮询到消息后交给自己处理,构造时需要传入Looper或者自动获取当前线程的Looper

Looper:轮询器,一个线程只能有一个Looper,用于从消息队列中轮询消息,然后交给对应的Handler处理

MessageQueue:消息队列,是一个阻塞队列,当没有消息是线程会被阻塞,等待有消息时唤醒线程

Message:消息对象,采用了对象池技术,可以避免对象的频繁创建开销

消息的发送和轮询

发送消息:最终都会调用MessageQueue的enqueueMessage中,如果队列中没有消息或者消息的发送时间小于第一个消息,则直接放入队列头部,否则则根据时间插入到队列中的合适位置,同时会刷新needWeak标记,然后判断needWeak,如果需要唤醒,调用naviteWeak唤醒Looper。

获取消息:核心方法为MessageQueue的next,首先调用nativePollOnce,这个方法是一个native阻塞方法(通过监控文件描述符的io操作来实现),新消息放入队列时,会调用naviteWeak来唤醒。唤醒之后,如果是消息屏障就先处理异步消息,否则判断消息执行时间是否到达来返回消息或者进入下一个循环继续阻塞。

IdleHandler:在消息队列空闲的时候,会尝试执行IdleHandler的任务

消息屏障机制:当messageQueue取出屏障消息时,会开启循环,找到下一个异步消息执行,相当于阻塞了同步消息而优先执行异步消息,Android中所有UI绘制都是异步消息,可以保证UI绘制任务优先执行。

postDelay怎么保证消息的顺序

发送消息时会对消息队列按执行时间进行排序,如果没有消息或者当前消息执行时间小于队列的第一条消息,则直接插入到表头,否则会插入到合适的位置,保证delay时间长的不会阻塞住时间短的。

Activity启动流程

1.Luancher 进程通过binder向AMS发起startActivity请求
2.AMS收到请求, ActivityStarter解析flag,启动模式等,ActivityStack处理activity栈
3.然后AMS通过socket调用到Zygote,fork新的app进程
4.app进程创建后,再通过binder向AMS发起attachApplication请求
5.AMS通过binder调用发送scheduleLaunchActivity到app进程
6.APP进程的binder线程ApplicationThread接收到请求,通过handler发送LAUNCH_ACTIVITY消息到主线程
7.ActivityThread接收到消息,执行到handleLaunchActivity,开始Activity的生命周期
Alt text
应用第一次启动时,zygote进程fork出应用进程后,AMS会保存一个ProcessRecord信息(包名+进程uid),下一次启动判断这个ProcessRecord已经存在的话,就不会再新建进程,这就属于应用内打开Activity的过程了

APK打包流程

Alt text

1.打包资源文件,生成R.java文件
2.处理aidl文件,生成相应的Java文件
3.编译项目源代码,生成class文件(所有的Java代码,包括R.java和.aidl文件)
4.转换所有的class文件,生成classes.dex文件
5.打包生成APK文件
6.对APK文件进行签名
7.对签名后的APK文件进行对齐处理。对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用

APK安装流程:

1.拷贝阶段:通过PMS通过handler发送一个安装消息,包含一些安装的参数,PackageHandler收到消息后通过隐式intent绑定到拷贝的service。检查apk安装路径,包的状态,然后拷贝至/data/app包名下
2.装载阶段:installPackageLI,PackageParser解析AndroidManifest文件,解析四大组件等信息
3.验证apk的签名信息
4.执行 dex 优化,实际为 dex2oat 操作,用来将 apk 中的 dex 文件转换为 oat 文件
5.安装apk,创建data目录,安装成功则更新权限等信息
6.发送成功广播,安装失败则删除安装包和缓存

Android系统启动流程

1.开机加载BootLoader,加载Linux内核
2.启动Init进程
3.读取init.rc配置文件,启动几个关键进程,包括Zygote、ServiceManager、SurfaceFlinger和MediaServer
4.zygote进程启动java runntime,然后fork出sytem_server进程
5.sytem_server启动AMS、WMS、电源管理、等等系统服务,并通过Binder注册到ServiceManager。
6.启动Launcher显示桌面
Alt text

Webview的漏洞

1.4.2版本的addJavascriptInterface 造成的远程代码执行漏洞,当JS拿到Android这个对象后,就可以调用Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而执行任意代码

4.2版本以上通过对调用的方法添加JavascriptInterface注解避免漏洞
4.2以下采用url拦截或者js弹窗拦截的方式进行交互,不能采用对象映射

2.密码明文存储
3.域控制不严问题,允许导出的WebActivity未关闭file协议,可以外部启动并加载恶意file协议的文件,从而访问私有文件,所以不需要使用file协议的,需要关闭

Dalvik与ART的区别

1.Dalvik:即时编译,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,所以运行效率相对ART较低。由于不需要预编译,所以安装过程较快
2.ART :应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应用的首次启动(安装慢了)会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高
3.ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%)
4.预编译也可以明显改善电池续航,从而减少了 CPU 的使用频率,降低了能耗。

热修复和插件化原理

java类加载机制

双亲委托机制:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

Java的类加载器分为:根加载器(加载java核心类)、扩展类加载器(加载jre扩展目录)、应用类加载器(加载classPath指定目录的类,自定义类加载器一般继承此加载器)

Android的类加载器:根加载器、BaseDexClassLoader、PathClassLoader(加载安装到系统的APK)、DexClassLoader(加载指定目录的dex和apk)

热修复和插件化的区别

热修复:替换bug的类,需要把修复类抢先于bug类进行加载,让bug类得不到加载。通过反射修改DexClassLoader中DexPathList的dexElements数组,将需要加载的dex添加到数组前面。

插件化:运行未安装的插件apk的代码,不涉及到抢先加载,只需要将dex加载到dexElements中

热修复的CLASS_ISPREVERIFIED问题

热修复是需要修复有bug的类,所以需要把dex放在数组的前端抢先加载补丁类,在虚拟机启动的时候,在verify选项被打开的时候,如果static方法、private方法、构造函数等,其中直接引用到的类都在同一个dex文件中,那么该类就会被打上CLASS_ISPREVERIFIED标志,且一旦类被打上CLASS_ISPREVERIFIED标志其他dex就不能再去替换这个类。
​ 为了阻止类被打上CLASS_ISPREVERIFIED标志,先将一个预备好的hack.dex加入到dexElements的第一项,让后面的dex的所有类都引用hack.dex其中的一个类。

常见热修复框架

Tinker实际上是类加载方案的升级,它增加了dex的差分算法,再将差分dex与apk中的classex.dex做合并,在运行bug类之前抢先加载补丁。

tinker差分算法:dexDiff , 新旧dex先排序,然后定义两个指针依次往下挪,如果old>new,则一定是新增,如果old<\new,则是删除,如果相等,则索引可能不同,需要记录索引的变化,最后如果相同的索引既有删除又有新增,则优化为replace操作。资源差分则采用BSDiff。

美团Robust,Instant Run方案,用代码插桩的方式,在每个方法中插入一段开关代码,如果需要修复,则走入if判断,执行补丁中的同名类的同名方法

插件化方案

插件化主要是加载新的功能模块,最主要的功能除了加载类,还需要加载资源和生命周期的管理。

资源加载:通过反射AssetManager ,将资源所在路径添加到AssetManager的path中,然后创建一个Resource,hook住activity替换mResources实现资源的访问。具体分为两种:

1.合并式:插件资源合并到主工程,插件和主工程可以直接访问资源,合并资源会造成资源id冲突,所以需要修改aapt源码,id格式为0xPPTTNNNN,在编译期修改高两位PP段,不同插件使用不同的PP段标识。
修改resources.arsc文件,该文件列出了资源id到具体资源路径的映射,相当于一个索引。

2.独立式:插件只访问插件内部的资源,不能共享,也不会冲突。

生命周期的管理:没有在清单文件中注册过的Activity不能启动,现在的主流做法是预留各种启动模式的代理Activity占坑,然后通过hook住mInstrumentation对象,在启动插件Activity时替换intent为代理的Activity,从而绕过AMS的验证,系统以为是启动的代理Activity,然后真正启动时,需要还原原先的Intent,启动插件Activity,同时hook替换mResources,这样就实现了生命周期管理

#VirtualApk原理
如何加载插件类:(类加载机制)
插件创建ClassLoader,父类加载器是宿主的classloader,这样插件可以反射访问到宿主的类。

然后判断COMBINE_CLASSLOADER标记,如果COMBINE_CLASSLOADER为true,则会把dex插入到父类加载器的elements后边,使宿主可以访问插件的类。如果为false表示宿主与插件隔离,宿主不能访问插件。不论true或false,插件都可以访问宿主。

DroidPlugin是采用隔离模式,插件的classloader的父类加载器是BootClassLoader,所以相互都不能访问。

如何加载资源:

1.COMBINE_CLASSLOADER为true:把插件资源添加到宿主Resources的AssetsManager的资源路径中去。
编译时过滤宿主和插件中的重复资源,然后修改R和资源表文件,让插件中只保留新的资源。
存在的问题:

2.宿主和插件开发可能是并行的,过滤资源时候如果依赖的是1.0的宿主,在1.1中资源发生了变化,插件就会找不到资源,滴滴是让public.xml让宿主资源id不可变来实现。
如果宿主和插件有相同名字的资源,例如都有一个about字符串,由于资源过滤,插件的会被过滤掉。

activity的启动:

hook系统instrumentation,判断如果启动的是插件的类,则通过预埋的activity绕过ams对activity的验证,然后在真正启动activity的时候,还原intent来启动目标类,这样目标类就有正常的生命周期了

service的启动:

hook系统ActivityManager,创建一个动态代理来替换系统中的单例对象,实际上启动LocalService来代理目标服务的生命周期。

广播:

解析清单文件,把注册的静态广播转换为动态广播。

启动activity时的问题

在创建插件的ClassLoader时,有一个COMBINE_CLASSLOADER标记用来设置是否要讲插件的dex插入到宿主的dex数组中,让宿主可以访问插件的类。COMBINE_CLASSLOADER标记为false时,宿主不能访问到插件类
源码instrumentation中handleMessage处理启动activity时,给intent的extras设置了宿主的类加载器,如果extra中有一个插件中才有的序列化对象,读取extra时反序列化会出错,就会抛出找不到类异常。到现在版本仍未解决。
解决方案:直接用一个新的intent包装原始的intent来替换,intent是parcelable的。

RecyclerView缓存

1.mAttachedScrap和mChangedScrap,用于缓存屏幕内的ViewHolder,例如下拉刷新后,屏幕内的ViewHolder需要刷新数据
2.mCachedViews,移除屏幕之外的,默认缓存两个,因为接下来可能马上往回滑动,再次使用到
3.ViewCacheExtension,预留的一个缓存扩展,暂时没有用到
4.RecycledViewPool,缓存屏幕外的 ViewHolder,需要重新绑定数据

RecyclerView 缓存结构,RecyclerView预取,RecyclerView局部刷新

  • Scrap:对应ListView 的Active View,就是屏幕内的缓存数据,就是相当于换了个名字,可以直接拿来复用
  • Cache : 刚刚移出屏幕的缓存数据
  • ViewCacheExtension:是google留给开发者自己来自定义缓存的
  • RecycledViewPool:回收池,最重要

    答:四级缓存,有一个自定义的不用

  • 局部刷新:notifyPositon或者nofiyview实现,可以执行动画

AsyncTask原理和缺陷

实际是通过Handler+线程池实现,内部含有两个线程池,一个用于排队,一个用于真正执行任务

缺陷:

执行任务的线程池是个静态的全局线程池,最大线程数为128,如果任务队列满了,然后最大线程数也满了,再提交任务会出现崩溃;解决方案为自定义线程池
必须主线程初始化,内部handler获取主线程looper,否则不能正确切换到主线程。
结果丢失问题:如果activity重建,例如切横竖屏,因为持有的引用是重建之前的,新的Activity无法接收到结果。
内存泄漏,退出页面需要正确取消

性能优化相关

卡顿优化

1.View本身绘制时间过长超过16ms造成掉帧,所以需要减少View嵌套层级,使用ViewStub和merge标签,优化过度绘制等

2.主线程执行耗时任务,合理使用线程,将耗时任务放到后台进行

3.内存抖动,会频繁触发GC,造成卡顿。使用内存分析工具优化内存使用,减少不必要对象的创建

耗电优化

合理使用后台服务,合并网络请求,使用protobuf替换json进行服务端请求,cpu休眠锁等

内存优化

内存泄露优化(包括常见的内存泄漏、分析方法、LeakCanrary等)、使用优化的数据结构(ArrayMap和SparseArray),对频繁创建的对象使用对象池技术,减少不必要的内存开销,

启动优化

Application的attachBaseContext和onCreate,三方sdk采用线程池异步初始化,activity的onCreate中,设置布局的优化(xml的io操作、反射创建view的操作,通过异步inflater和自定义inflater.factory来优化)

1.延迟初始化非必要的库,拓展到异步启动器的设计和实现,有向无环图来解决库依赖的问题。
2.给闪屏页设置图片背景避免冷启动白屏,其实不能加快启动,只是避免白屏给用户的体验不佳

apk大小优化

1,开启混淆压缩代码
2,压缩和混淆资源,图片压缩采用webP格式,整理Raw、assets资源
3,减少非必要so库,目前主流的机型都是支持armeabi-v7a的,并且armeabi-v7a兼容armeabi
4,移除未使用的资源,如图标,字符串,字体等
5,一些小图像可以使用矢量图,大矢量图渲染时间很长,不适用
6,减少三方库使用,避免枚举的使用
7,动态下发一些资源,如换肤包,so,字体等

网络优化

1.HttpDNS优化,传统DNS解析一般是用UDP的方式与DNS服务器交互,HttpDNS采用Http协议交互,绕过运营商的 LocalDNS 服务器,有效的防止了域名劫持,提高域名解析的效率
2.使用缓存,需要服务端支持,或者通过OkHttp拦截器添加统一的缓存策略
3.HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。在HTTP1.1中默认开启keep-alive
4.数据压缩,gzip压缩,http2.0也支持header的压缩
5.根据网络质量来下载不同质量的图片

View inflate流程

1.先从resource中获取一个xml parser用于加载布局
2.读取layout文件,调用createViewFromTag创建View
3.tryCreateView中,依次判断有没有设置Factory2和Factory,如果有则调用它的createView来创建view
4.如果没有factory则调用自己的createView来创建,内部是使用反射创建对象
5.注意,Factory2继承Factory增加了一个创建view的方法,相当于是一个扩展,它们可以对view创建的过程进行拦截,在创建view的时候做一些事情,例如换肤功能就用到这里。

换肤的原理

1.制作皮肤包apk,只包含颜色、图片等,通过网络下载到sd卡;
2.通过反射构造皮肤包的AssetManager,再用此AssetManager创建Resource。
3.原apk和资源包中资源名称一样,提供一个资源映射的方法,通过原来的资源id找到资源名称,然后在皮肤包中查找皮肤资源具体的值;
4.通过自定义LayoutInflater传入自定义的Factory2拦截view的创建过程,查找到需要换肤的view和对应的可替换属性名(background、color、textcolor等)、属性值的类型(color\drawable\mipmap…)、属性值在原apk中的资源名(如color1)和资源的id,保存起来;
5.点击换肤时,遍历需要换肤的view集合,调用对应的方法(setColor等等)设置新的值。

Android的startActivityForResult的实现为什么不使用回调。

因为匿名内部类会持有外部类的引用,使用回调时,例如A启动B去获取result,由于某些原因原A已经被销毁了,当B设置结果返回A时,实际上A已经被系统重建,和原先的A不是同一个对象了,所以就不能正确的获取结果。

如何跨app启动activity?

1.shareUserId,设置同一个shareUserId的应用可以直接启动。
2.Exported 设置为true,向外部暴露activity,允许外部启动。
注意:为了安全需要添加自定义权限控制,注意被暴露的有权限的app需要先被安装,否则会获取不到权限。
会导致拒绝服务漏洞,例如:A启动B中的activity,往intent中添加一个序列化对象,这个对象只在A中有,B中没有这个类,如果在B中访问intent的extra,就会触发反序列化对象,由于找不到这个对象的类,造成B崩溃。处理方法:获取extra要捕获异常。
3.隐式启动activity,只要intentFilter匹配成功就可以启动。

高性能日志采集

传统直接读写文件的方式的缺点

读写文件的IO操作,需要两次拷贝,用户空间到内核空间,内核空间再到硬盘。为了避免频繁IO,采用缓存日志到内存,达到一定量时再统一写入文件,虽然避免了频繁IO,但是可能造成crash时日志丢失。多进程也无法保证写入顺序

mmap方案:

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。

mmap的回写时机:

1.内存不足
2.进程退出
3.调用 msync 或者 munmap
4.不设置 MAP_NOSYNC 情况下 30s-60s(仅限FreeBSD)

多进程写入映射同一个内存也会造成写入顺序无法保证,所以可以选择多进程映射不同的文件,每隔一段时间合并一次来解决

性能优化工具:

查看方法执行时间:

TraceView(影响性能)

1.用Debug.startMethodTrace来打点,生成trace文件,然后用adb导出分析
2.使用profiler工具,选择cpu,输出trace文件

Systrace(轻量级)

1.TraceCompat.beginSection
2.使用python命令导出html,查看wall time和 cpu time

MAT分析内存泄露:

profiler分析内存,手动触发gc,然后输出堆转储hporf文件,然后用mat分析引用链

内存抖动:

使用profiler工具排查

你们项目的稳定性如何?有做过什么稳定性优化的工作?

答: 我们主要优化了三项:
Crash专项优化
性能稳定性优化
业务稳定性优化
性能:全面的性能优化:启动速度、内存优化、绘制优化
线下发现问题、优化为主
线上监控为主
Crash专项优化
我们针对启动速度,内存、布局加载、卡顿、瘦身、流量、电量等多个方面做了多维的优化。

我们的优化主要分为了两个层次,即线上和线下,针对于线下呢,我们侧重于发现问题,直接解决,将问题尽可能在上线之前解决为目的。而真正到了线上呢,我们最主要的目的就是为了监控,对于各个性能纬度的监控呢,可以让我们尽可能早地获取到异常情况的报警。

同时呢,对于线上最严重的性能问题性问题:Crash,我们做了专项的优化,不仅优化了Crash的具体指标,而且也尽可能地获取了Crash发生时的详细信息,结合后端的聚合、报警等功能,便于我们快速地定位问题。

PathClassLoader与DexClassLoader有什么区别

答: PathClassLoader 和 DexClassLoader 类加载器都是继承自 BaseDexClassLoade
DexClassLoader 和 PathClassLoader 构造函数
DexClassLoader 和 PathClassLoader 的差异在于构造 ClassLoader 对象时,是否给父类 (
BaseDexClassLoader
) 传递 optimizedDirectory 参数

跨进程通信了解多少?管道了解吗?

  • 答:四大组件可以跨进程
    主要是binder机制。Android都是,平时用AIDL
    管道:2次拷贝???????缓冲区有大小限制。管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。在创建时分配一个page大小的内存,缓存区大小比较有限。