精华内容
下载资源
问答
  • 对象间通信Signal和Slot机制

    千次阅读 2014-01-02 14:13:17
    信号与插槽机制提供了对象间通信机制,它易于理解和使用,并完全被Qt图形设计器所支持。 图形用户接口的应用需要对用户的动作做出响应。例如,当用户点击了一个菜单项或是工具栏的按钮时,应用程序会执行某些代码。...

    在Qt的众多与众不同的特点中,信号(Signal)/槽(Slot)机制是Qt的一个中心特征并且也许是Qt与其它工具包的最大不相同的部分。信号和槽主要用于对象之间的通讯。

    信号与插槽机制提供了对象间通信机制,它易于理解和使用,并完全被Qt图形设计器所支持。

    图形用户接口的应用需要对用户的动作做出响应。例如,当用户点击了一个菜单项或是工具栏的按钮时,应用程序会执行某些代码。大部分情况下,我们希望不同类型的对象之间能够进行通信。程序员必须把事件和相关代码联系起来,这样才能对事件做出响应。以前的工具开发包使用的事件响应机制是已崩溃的,不够健壮的,同时也不是面向对象的。Trolltech已经创立了一种新的机制,叫做“信号与插槽”。信号与插槽是一种强有力的对象间通信机制,它完全可以取代原始的回调和消息映射机制;信号与插槽是迅速的,类型安全的,健壮的,完全面向对象并用c++来实现的一种机制。在以前,当我们使用回调函数机制来把某段响应代码和一个按钮的动作相关联时,我们通常把那段响应代码写成一个函数,然后把这个函数的地址指针传给按钮,当那个按钮被按下时,这个函数就会被执行。对于这种方式,以前的开发包不能够确保回调函数被执行时所传递进来的函数参数就是正确的类型,因此容易造成进程崩溃,另外一个问题是,回调这种方式紧紧的绑定了图形用户接口的功能元素,因而很难把开发进行独立的分类。Qt的信号与插槽机制是不同的。Qt的窗口在事件发生后会激发信号。例如一个按钮被点击时会激发一个“clicked”信号。程序员通过建立一个函数(称作一个插槽),然后调用connect函数把这个插槽和一个信号连接起来,这样就完成了一个事件和响应代码的连接。信号与插槽机制并不要求类之间互相知道细节,这样就可以相对容易的开发出代码可重用的类。信号与插槽机制是类型安全的,它以警告的方式报告类型错误,而不会使系统产生崩溃。例如,如果一个退出按钮的clicked()信号被链接到了一个应用的退出函数-插槽quit()。那么一个用户点击退出键俺就的clicke()信号被连接到一个应用的退出函数-插槽quit()。那么一个用户点击退出键将使应用程序终止运行。上述的链接过程用代码写出来就是这样

    connect(button,SIGNAL(clicke()),qApp,SLOT(quit()))

    我们可以在Qt应用程序的执行过程中增加或是减少信号与插槽的链接。信号与插槽的实现扩展了C++的语法,同时也完全利用了C++面向对象的特征。信号与插槽可以被重载或者重新实现,他们可以定义为类的公有,私有或是保护成员。

    信号:当对象的内部状态发生改变,信号就被发射,在某些方面对于对象代理或者所有者也许是很有趣的。只有定义了一个信号的类和他的子类才能发射这个信号。

    例如,一个列表框同时发射highlighted()和activated()这两个信号。绝大多数对象也许只对activated这个信号感兴趣,但是有时想知道列表框中的那个条目在当前是高亮的。如果两个不同的类对同一个信号感兴趣,你可以把这个信号和这两个对象链接起来。当一个信号被发射,它所连接的槽会被立即执行,就像一个普通函数调用一样。信号/槽机制完全不依赖与任何一种图形用户界面的事件回路。当所有的槽都返回后emit也就返回。

    如果几个槽被链接到一个信号,当信号被发射时,这些槽就会被任意顺序一个接一个地执行。

    槽:当一个和槽链接的信号被发射的时候,这个槽被调用。槽也是普通的C++函数并且可以像他们一样被调用:他们唯一的特点就是他们可以被信号链接。槽的参数不能含有默认值,并且和信号一样,为了槽的参数而使用自己特定的类型是很不明智的。

    因为槽就是普通成员函数,但却有一点非常有意思的东西,他们也和普通成员函数一样有访问权限。一个槽的访问权限决定了谁可以和它相连:

    一个publicslots:包含了任何信号都可以相连的槽。这对于组件编程来说非常有用:你生成了许多对象,它们互相并不知道,把他们的信号与槽链接起来,这样信息就可以正确地传递,并且就像一个铁路模型,把它打开然后让他跑起来。

    一个protectedslots:包含了之后这个类和他的子类的信号才能链接的槽。这就是说这些槽只是类的一部分,而不是它和外界的接口

    一个privateslots:包含了之后这个类本身的信号可以链接的槽。这就是说它和这个类是非常紧密的,甚至它的子类都没有获得链接权利这样的新人

    你也可以把槽定义为虚的,这在实践中被发现也是非常有用的。

    connect(object1,signal1,object2,slot1)

    connect(object1,signal1,object2,slot2)

    object1                                                                                               object2

    signal1-----------------------------------------------------                        signal1

    signal2-------------------------------------------              |                     signal2

    slot1                                                              |             |----------------slot1

    slot2                                                              |---------------------------slot2   


    信号与槽的机制是非常有效的,但是它不像“真正的”回调那样快。信号与槽稍微有些慢,这是因为他们所提供的灵活性,尽管在实际应用中这些不同可以被忽略。通常,发射一个和槽相连的信号,大约只比直接调用那些非虚函数调用的接受器慢十倍。这是定位链接对象所需的开销,可以安全地重复所有的链接(例如在发射期间检查并发接收器是否被破坏)并且可以按一般的方式安排任何参数。当是个非虚函数调用听起来很多时,举个例子来说,时间开销只不过比任何一个“new”或者"delete"操作要少些。当你执行一个字符串、矢量或者列表操作时,需要“new”或者“delete”,信号和槽仅仅对一个完整函数调用的时间开销中的一个非常小的部分负责。无论何时你在一个槽中使用一个系统调用和间接地调用超过十个函数的时间是相同的。在一台i585-500机器上,你每秒钟可以法神2,000,000个左右连接到一个接收器上的信号,或者发射1,200,000个左右链接到两个接受器的信号。信号和槽机制的简单性和灵活性对于时间的开销来说是非常值得的,你的用户甚至察觉不出来。


    展开全文
  • Binder进程间通信机制的Java接口

    千次阅读 2015-03-16 12:27:18
    概述Java代码可以通过JNI方法来调用C/C++代码,因此,Android系统在应用程序框架层中提供了Binder进程间通信机制的Java接口,它们通过JNI方法来调用Binder库的C/C++接口,从而提供了执行Binder进程间通信的能力。...

    概述

    Java代码可以通过JNI方法来调用C/C++代码,因此,Android系统在应用程序框架层中提供了Binder进程间通信机制的Java接口,它们通过JNI方法来调用Binder库的C/C++接口,从而提供了执行Binder进程间通信的能力。
    主要从以下几个使用场景来分析Binder进程间通信机制的Java接口:

    1. ServiceManager的Java代理对象的获取过程。
    2. Java服务接口的定义和解析。
    3. Java服务的启动过程。
    4. Java服务代理对象的获取过程。
    5. Java服务的调用过程。

    为了方便描述,在Java层中,我们将Service组件成为Java服务;相应的,将Service组建的代理对象称为Java服务代理对象。


    Service Manager的Java代理对象获取过程

    在Java层中,Service Manager的Java代理对象类型为ServiceManagerProxy,它实现了IServiceManager接口,如下图所示:


    Java服务接口的定义和解析

    在实现自己的Java服务之前,首先要定义这个Java服务要实现的接口,即定义自己的Java服务接口。在Android应用程序中,我们可以通过Android接口描述语言(Android Interface Definition Language, AIDL)来定义Java服务接口。AIDL是一种Binder进程间通信接口的描述语言,通过它来定义的Java服务接口具有执行Binder进程间通信的能力。
    以AIDL定义的Java服务接口保存在一个以”.aidl”为后缀名的文件中,在编译时,它们会被转化为使用Java语言描述的Java服务接口。这里,我以一个音乐播放器的例子,来介绍一下Java服务的实现。
    音乐播放器服务接口IRemoteService定义在文件IRemoteService.aidl中,文件内容如下所示:

    package com.example.photocrop.service;
    
    public interface IRemoteService extends android.os.IInterface {
        /** Local-side IPC implementation stub class. */
        public static abstract class Stub extends android.os.Binder implements com.example.photocrop.service.IRemoteService {
            private static final java.lang.String DESCRIPTOR = "com.example.photocrop.service.IRemoteService";
    
            /** Construct the stub at attach it to the interface. */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an
             * com.example.photocrop.service.IRemoteService interface, generating a
             * proxy if needed.
             */
            public static com.example.photocrop.service.IRemoteService asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.example.photocrop.service.IRemoteService))) {
                    return ((com.example.photocrop.service.IRemoteService) iin);
                }
                return new com.example.photocrop.service.IRemoteService.Stub.Proxy(obj);
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return this;
            }
    
            @Override
            public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
                    throws android.os.RemoteException {
                switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_play: {
                    data.enforceInterface(DESCRIPTOR);
                    this.play();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_stop: {
                    data.enforceInterface(DESCRIPTOR);
                    this.stop();
                    reply.writeNoException();
                    return true;
                }
                }
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements com.example.photocrop.service.IRemoteService {
                private android.os.IBinder mRemote;
    
                Proxy(android.os.IBinder remote) {
                    mRemote = remote;
                }
    
                @Override
                public android.os.IBinder asBinder() {
                    return mRemote;
                }
    
                public java.lang.String getInterfaceDescriptor() {
                    return DESCRIPTOR;
                }
    
                @Override
                public void play() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
    
                @Override
                public void stop() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
    
            static final int TRANSACTION_play = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
    
        public void play() throws android.os.RemoteException;
    
        public void stop() throws android.os.RemoteException;
    }
    

    文件定义了一个Java接口IRemoteService,一个Java抽象类IRemoteService.Stub和一个Java类IRemoteService.Stub.Proxy,其中,Java抽象类IRemoteService.Stub是在Java接口IRemoteService内部定义的,而Java类IRemoteService.Stub.Proxy是在Java抽象类IRemoteService.Stub内部定义的。
    IRemoteService.Stub和IRemoteService.Stub.Proxy类都实现了IRemoteService接口,其中,IRemoteService.Stub类用来描述一个Java服务,IRemoteService.Stub.Proxy类用来描述一个Java服务代理对象。我们要实现的音乐播放服务PlayerService就是以IRemoteService.Stub为父类的。
    IRemoteService.Stub类的静态成员函数asInterface通常用来将一个Java服务代理对象,即一个BinderProxy对象封装成一个IRemoteService.Stub.Proxy对象,即一个实现了IRemoteService接口的Java服务代理对象。
    IRemoteService.Stub类的成员函数onTransact是用来接收和分发进程间通信请求的。当Client进程向一个实现了IRemoteService接口的Java服务发送一个类型为TRANSACTION_setVal或者TRANSACTION_getVal的进程间通信请求时,IRemoteService.Stub类的成员函数onTransact就会被调用,它分别将这两种类型的进程间通信请求分发给由其子类所重写的两个成员函数setVal和getVal来处理。
    IRemoteService.Stub.Proxy类内部有一个成员变量mRemote,它指向的是一个Java服务代理对象,即一个BinderProxy对象,用来向一个实现了IRemoteSrvice接口的Java服务发送进程间通信请求。
    IRemoteService.Stub.Proxy类实现了IRemoteService接口的两个成员函数:setVal和getVal,它们分别向一个实现了IRemoteService接口的Java服务发送一个类型为TRANSACTION_setVal和TRANSACTION_getVal的进程间通信请求。
    得到了使用Java语言描述的Java服务接口之后,接下来我们继续分析Java服务的启动过程、Java服务代理对象的获取过程,以及Java服务的调用过程。

    展开全文
  • 原因是组件都是给系统框架调用的,开发者只能实现其规定的回调接口,组件的创建与销毁都是由系统框架控制的,开发者不能强行干预,更没有办法获取组件的对象。比如Activity,Service,BroadcastReceiv

    组件的特点

    对于Android的四大组件Activity, Service, ContentProvider和Service,不能有Setter和Getter,也不能给组件添加接口。原因是组件都是给系统框架调用的,开发者只能实现其规定的回调接口,组件的创建与销毁都是由系统框架控制的,开发者不能强行干预,更没有办法获取组件的对象。比如Activity,Service,BroadcastReceiver,你没有办法去创建一个Activity,Service或BroadcastReceiver,然后像使用其他类那样的调用其上的接口与其通信,用Setters和Getters改变属性等等。这也决定了,组件之间通信只能用系统支持的Intent。而Intent只能传递基本数据类型和Uri等一些常见的数据类型。Intent只支持传递内置类型和一些限制类型,这就导致了组件之间的数据传递必须都是基本类型,所以枚举类型无法使用。

    多态无法实现

    比如你有一个Service用于在后台执行UI中发来的请求,这些请求有些是做数据请求,有些是做数据分析,等等。这里可以用多态,定义一个统一的Transaction类,然后再为每种特定的Transaction类型,Transaction中统一接口process()用于实际的处理,理想的情况是,Service接收一个Transaction对象,然后调用其process(),没有必要知道具体的类型,UI创建具体的一个类型对象然后交由Service来处理。但是这在Android当中是无法实现的,因为Intent通信机制所限,因为它不能直接传递Transaction对象。所以,Service必须要知道具体的类型。原生应用Mms中就有如此的现象,在transaction包中TransactionService是处理服务,UI发送到Service的只是区别不同Transaction的Id(一个整数),Service查看不同的Id创建不同的Transaction对象,然后调用process()对其处理。
    建议:自己实现一个类似Service的服务类,在其内用Handler,Thread和Looper让其长时间运行。这样就没有组件间通信的限制,你可以像正常使用Java对象那样来使用这个服务类,向其传递自定义的处理请求:
    public class TransactionServer extends HandlerThread {
        public TransactionServer() {
            start();
        }
        public void onLooperPrepared() {
            mHandler = new Handler(getLooper(), new Handler.Callback() {
                 @Override
                  public void handleMessage(Message msg) {
                        Transaction request = (Transaction) msg.obj;
                         request.process();
                   }
             }
        }
        
       public void execute(Transaction request) {
           if (mHandler == null) {
                return;
            }
           Message msg = Message.obtain();
           msg.obj = request;
           mHandler.sendMessage(msg);
       }
    }


    在Activity中就可以创建此Server的对象,然后使用它
    TransactionServer server = new TransactionServer();
    Transaction updateRequest = new UpdateTransaction();
    server.execute(updateRequest);


    另外,用AIDL与Service通信,虽可以获取Service的对象引用,可以直接调用Service的方法,但这个也有限制,对于AIDL的接口,所有的参数和返回类型都必须是基本数据
    据类型,不能有对象。原因也好理解,因为AIDL也是要通过IPC的,即便Service与Activity在同一个进程内,所以本质上它与Intent通信机制无区别。

    封装性被破坏

    组件间的通信机制决定了Android的封装性,先来看一些实例:

    Intent i = new Intent(Intent.ACTION_VIEW);
    i.setDataAndType(uri, "text/html");
    startActivity(i);
    

    这在Android当中是再常见不过的了。

    Intent和IntentFilter的使用让封装性受到大大的破坏,因为你必须把字串,参数等直接写入到Intent或IntentFilter当中。例如:
    Intent i = new Intent("android.contacts.action.MULTIPLECONTACTSLISTS");
    i.setExtra("request_type", 3);
    
    <intent-filter>
         <action android:name="android.contacts.action.MULTIPLECONTACTSLISTS" />
    </intent-filter>


    当然,可以再好一点,就是:
    Intent i = new Intent(Contacts.ACTION_GET_CONTACTS);
    但是在AndroidManifest中的IntentFilter还是要写字串常量(Literal Strings),这样就有一个问题,就是即使你写错了,编译器不会提醒你,直到你运行的时候才会发现程序不正常工作,你调试啊,调试,最终发现是字串写错了。或者,activity的name写错了,编译器同样不会提醒你,但你运行时却因找不到类而报出RuntimeException。
    建议:尽可能在所有作用域内定义常量,以让组件间接口保持一致性,特别是对于字串常量,一定要在二个组件都可见的作用域内定义常量,否则将会有维护的麻烦。
    例子:intent.putExtra("request_type", 3); --> intent.putExtra(TargetActivity.REQUEST_TYPE, TargetActivity.NO_BACKGROUND);否则,特别是当目标组件不在同一个包内,或距离很远时,如果另一方改了,编译时不会有错,但程序不会正常工作,从而引发难以发觉的Bug

    无法在组件间传递自定义的数据结构

    如前面所述,因为无法获取组件的对象的引用,所以你无法向其设置数据,当然,可以用静态方法,但是不优雅且难以维护(对于Service倒是可以通过AIDL方式获取Service对象的引用,然后调用其方法来添加数据)。又因为Intent只能携带基本的数据类型,所以对于自定义的数据结构想要在组件间传递就特别的麻烦。当然你可以以让数据结构实现Parcleable接口,但是用起来也相当的麻烦。
    建议:
    1. 尽可能的避免使用自定义数据结构,特别是除了Setters和Getters以外不具有其他行为的数据结构
    对于结构化的数据,为其定义ContentProvider,把数据写入SQLite数据库,这样数据库表中的每行数据都相当于是一个数据对象,每一列都是其属性。因为Android的组件与SQLite数据库的粘性很大,每个组件都可以很方便的从数据库中获取数据,再通过Cursor等工具来操作数据。最最重要的是这很方便在组件间传递数据,因数ContentProvider的访问都是通过Uri来实现的,而Uri又可以与Intent无缝接合,Uri可以方便的放入和从Intent中取出,每个组件又都可以直接访问ContentProvider用Uri读取数据,从而就可以实现组件间的无缝数据传递。
    2. 尽可能的不要在组件之间传递数据
    不要用太多的Activity,Service也能免则免,Activity+线程可能解决大部分问题,当然了,线程也不是那么好用的,特别是在Android里面。
    3. 避免在组件之间传递自定义数据结构
    如前所述,组件之间最好直接传递基本数据和Intent支持的数据类型。对于自定义的数据结构,要么不定义数据结构,要么不要在组件间传递,否则会很麻烦,虽然可能以实现Parcelable接口,但是效率和操作的方便性上都会大打折扣。

    关于枚举和整数集

    先前一篇博客曾说要尽量使用枚举(enum)代替整数集(ints),而且很多编程书籍(Effective Java)也建议用枚举代替整数集,这其中的好处就是降低出错率,把运行时的检查可以放到编译时,因为整数的范围较大,你可传递任意的整数,直到运行时才会检测或产生问题,但是枚举会在编译时检查类型,如果不是合法的枚举,编译器会报怨。
    但是我们可以看到,在Android中的情况却很差,Android中大量的使用了整数集,系统定义了大量的整数集,很多参数也都是整数,虽然正确的方法都是向这些API传递其所定义的整数常量,但是你如果传个Integer.MAX_VALUE或Integer.MIN_VALUE,起码在编译时不会出问题。
    既然这不是一个好的编程规范,为什么Android中还要大量的使用整数集呢?原因就在于组件间通信,组件之间要传递参数,但是Intent又只能放入基本数据类型,也就是说如果使用枚举,那么将无法用Intent传递给其他的组件,因为枚举转为整数很容易,但反过来整数转成枚举就不是那么容易了。
    所以,如果你的常量不需要在组件间来回的传递,那么最好定义成为枚举,否则,只能用整数集了。

    关于组件一般的设计原则

    1. 不要用组件实现某些接口,比如点击接口,等等
    因为组件是一个开销非常巨大的对象,组件的继承层次也非常的深,用组件实现接口,传递给调用者,就相当于用一个火车去运送一个小老鼠一样,给了别人一个相当大的对象,但是仅有一个或二个方法是别人需要的。特别是对于Activity,不要去实现一些公共的接口比如View.OnClickListener,除了前面的原因以外,另外一个就是你的onClick必须用条件来区分点击的是哪个UI元素,这很难维护,还有一个原因就是Activity的对象不是很稳定,因为系统的某些事件如转屏,语言切换等等会把Activity杀死并重新创建一个实例,所以有可能会引发问题,虽然看起来Activity还在,但是并不同一个实例,如果某些东西与具体实例相关,就会引发问题,要么程序不正常工作,要么有RuntimeException。还有可能引发内存泄漏,因为送给使用者的接口对象都是Activity的实例引用,一旦某个引用超过Activity的生命周期,就会造成内存泄漏。
    推荐的做法是用匿名内部类来实现接口,如果其他地方需要对此接口对象的操作,可以声明一个成员变量或者一个内部类,这样也方便Activity来控制,以保证所有东西都生存在Activity的生存周期之内。
    2. 少用Service
    组件Service并没有传说中的好用,而且它还会让你的程序退出页面后仍然在后台跑,占系统资源不说,还会被骂(看看这些文章吧),因为Service的生命周期是由系统来控制,我们无法干预,即使你确切的知道某些时候你已经完全不用它了。用Activity和线程就可以完成绝大多数操作,而且你还能做到让所有线程都在Activity的控制之内,让它们都活在Activity的生命周期之内。另外的原因就是,因为线程都属于自建的类,或者普通的Java类,可以应用面向对象,因为没有了组件通信的限制。
    3. 利用ContentProvider来做复杂数据结构的通信工具
    ContentProvider和SQLiteDatabase存储的就是结构化数据,相当于一个数据结构,它的引用就是它的Uri,任何组件通过Uri就可获得此数据结构。它有如下优点:
    1. 可以方便的在组件间传递
    因为数据实际是在数据库中,你在组件间仅传递其地址Uri即可,任何组件或任何持有Context的类都可以方便的获取它,无论从实用性还是从效率上讲,这比用Intent传,或者实际传送数据结构对象来得快。
    2. ContentProvider组件有自己的进程和线程,不会有线程同步问题
    外部都是通过ContentResolver来访问ContentProvider,因此ContentProvider对外界来讲是一样的,访问方式相同,自然就不会有线程同步之类的问题。
    3. ContentProvider可以进行封装,从而使数据操作更加方便
    因为ContentProvier提供统一的接口,你可以利用数据自身的特点,在实现这些接口时进行一些封装,比如添加默认值等等。
    4. ContentProvider可以用作队列或堆栈
    因为每一行都是一个结构化数据,每一行的数据插入的顺序又是按先后顺序,所以这完全可以当做一个队列,或一个堆栈。
    可以参考原生Mms中信息的发送流程,信息从用户点击发送就写入数据库,然后一路把其Uri在各个组件间中传递,每个组件更新信息的状态,直到最后发送。还有DownloadProvider,Android中默认的下载,应用程序通过DownloadManager提交一个Request,但实际做下载的是DownloadService,而DownloadServer是在packages/provider/DownloadProvider中,是一个完全独立的进程。DownloadManager仅是把一个Request写入DownloadProvider中,这个Request包含下载一个东西的相关信息如URL等。DownloadService仅是监听DownloadProvider的变化,一旦有新数据插入,就创建线程读出此Request,然后开始下载。下载的同时,也是把数据直接更新到DownloadProvider中,这样UI就可以显示进度等信息。这一过程涉及二个进程,至少三个组件:提交Request的用户进程和DownloadProvider进程,DownloadManager(是一个公共API),DownloadService(单独进程,私有的package)和DownloadList(在DownloadProvider包内部,用于显示下载进度的UI),这些组件之间没有直接的通信,它们都是围绕着ContentProvider。同时这里的ContentProvider也被用做下载请求的队列,DownloadManager可以不断的向其中加入请求,DownloadService会监听其变化从其中取出数据然后做下载。

    别说Android开发很简单

    虽然Android上手很容易,但是要想写出优质的代码并不简单,分裂现象,碎片化,系统架构等等都给很多事情加大了难度。可以看一下原生应用中的主要的Activity代码量都在5000行以上,它们的界面比较复杂,是主要核心业务逻辑所在,这些Activity控制的东西比较多,所以很臃肿。当然这里的主要原因,还是未能进行良好的设计和重构。比如ICS中的Browser就做的好一些,它的BrowserActivity只有几百行的代码,但以前的代码却是6000多行,现在它把各种业务逻辑分别拆开,Activity只负责接收Frameworks层的回调,所有的业务逻辑控制交由Controller来完成,而Controller只负责Tab的管理,菜单等的管理。具体的菜单和布局分辨率相关的东西又交由PhoneUi来处理。下载的处理由DownloadHandler来处理,等等。原来这些所有的事情都放在了BrowserActivity中的,可以想像原来它里面的逻辑会是多么的乱,维护起来会是多么的痛苦。当然,现在的设计也还有待提高,因为类之间的耦合依然很大,比如Controller中持有PhoneUi对象,但是PhoneUi对象又持有Controller,等等现象。很多时候会出现相互调用的情况,这是相当难以维护的,也破坏了相当多的设计原则。

    总之,凡是程序,如果要想写的好,都需要投稿额外的精力,平台虽然有优劣但更重要的是对代码投入的精力。但现在可悲的是,Android平台赢利不理想,加之碎片化和浮躁的心理,使得很多应用都在一二个月内做出来,所以整个Android生态系统中的应用质量都不高,更为严重的是反编译和克隆,很多人都是把应用抓下来,反编译然后改了改就是一个新的应用,越是如此不关注质量,用户就越不买帐,开发者无法赢利,就越难投入精力做好应用,如此进入了一个恶性循环。

    展开全文
  • 前言:由于Android系统本身决定了其自身的单线程模型结构。在日常的开发过程中,我们又不能把所有的工作都交给主线程去处理...接下来,我们从Android线程间通信机制和Android消息机制两个方面对以上内容进行介绍。 ...

    前言:由于Android系统本身决定了其自身的单线程模型结构。在日常的开发过程中,我们又不能把所有的工作都交给主线程去处理(会造成UI卡顿现象)。因此,适当的创建子线程去处理一些耗时任务是非常关键的。同时Android中非UI线程不能对UI组件进行操作,因此,熟练的掌握并应用线程间消息通信是很有必要的。接下来,我们从Android线程间通信机制和Android消息机制两个方面对以上内容进行介绍。

    一.Android线程间通信机制

    Android的线程间通信主要是在非UI线程对UI线程的消息传递,并且修改UI界面的操作。Android中常见的子线程更新UI的方式:

    1. handler.post(Runnable r)
    2. runOnUiThread(Runnable r)
    3. view.post(Runnable r)
    4. Handler+Message+MessageQueue+Looper

    接下来我们依次看一下这几种调用方法的内部实现原理。

    - handler.post(Runnable r)内部实现原理:

    //Handler.post()方法:
     public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    //Handler.sendMessageDelayed()方法:
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    //Handler.sendMessageAtTime()方法:
     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);
        }

    调用链:Handler.post->sendMessageDelayed->sendMessageAtTime
    从上面的调用链可以看出,Handler.post方法最终调用了Handler.sendMessageAtTime方法,而通过sendMessageAtTime的实现逻辑可以看出,最终还是通过enqueueMessage方法将Message插入到消息队列,进行轮询处理。因此,底层还是Handler消息机制。

    - runOnUiThread(Runnable r)内部实现原理:

     public final void runOnUiThread(Runnable action) {
            if (Thread.currentThread() != mUiThread) {
                mHandler.post(action);
            } else {
                action.run();
            }
        }

    runOnUiThread内部实现机制到这里基本就可以结束了,因为它的实现原理一目了然。简单的判断当前线程是否是UI线程,如果是,就直接执行Runnable方法。如果不是,就通过Handler.post方法处理这个Runnable。因此,底层还是Handler消息机制。。

    - view.post(Runnable r)内部实现原理

    public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
    
            // Postpone the runnable until we know on which thread it needs to run.
            // Assume that the runnable will be successfully placed after attach.
            getRunQueue().post(action);
            return true;
        }

    和runOnUiThread内部实现相似,view.post方法也是通过Handler.post方法处理这个Runnable。

    从上面的分析可以看出,所有的自线程更新UI组件的方式都是依靠Handler消息机制实现的。因此,接下来我们将详细介绍Android的消息机制-Handler消息机制。

    二.Android消息机制(Handler消息机制)

    Handler消息机制的运行需要底层的MessageQueue和Looper来支撑,它的主要作用是将一个任务放到某个指定的线程中去执行。接下来我们详细介绍Handler、Message、MessageQueue、Looper的作用和联合工作机制。

    MessageQueue:中文意思是消息队列。顾名思义,它的内部存储了一组消息,以队列的形式对外提供添加和删除操作。可能很多小伙伴从它的字面意思,就认为它就是一个队列的数据结构。其实并不是,它的内部是采用单链表的数据结构来存储消息列表,最初的存在形式就是一个空的头节点(mMessage)。

    Message:中文含义为消息。很明显,它就是我们上面说到的MessageQueue中存储的实体。上面说到,MessageQueue是以单向链表的形式存储数据,每一个Message都是其中的一个节点。因此开始时,队列为空,即没有需要处理的消息。当有消息到来时,就添加节点到队列尾部(添加新节点到链表尾部,记录Next域)。
    Message数据结构:

    public final class Message implements Parcelable {
    
        public int what;
    
        public int arg1;
    
        public int arg2;
    
        public Object obj;
    
        public Messenger replyTo;
    
        //记录下一个节点域
        android.os.Message next;
    }

    上面的Message结构只是简单罗列了一些我们常用的字段,其中实现链表最关键的字段就是next字段,它是实现消息队列的核心。我们创建Message对象有多种方式,但是建议通过Message的obtain方法去获取Message对象,而不是直接创建新的Message对象。想了解更多关于obtain方法内部实现原理,请移步:Handler消息机制之深入理解Message.obtain()

    Looper:中文含义为循环,在这里即为消息循环。由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper正好弥补了这个缺陷。Looper会以无限循环的方式去查找是否有新的消息,如果有消息,就取出来处理,否则就一直等待。除此之外,它还有一个特殊的概念:ThreadLocal(存储和获取当前线程的Looper),在这里不作过多阐述,想要了解ThreadLocal是如何实现线程私有化存储机制的,可以移步 ThreadLocal内部实现机制详解 查看详情。
    Handler:主要功能是切换到指定线程去处理Looper轮询查找到的新消息。

    Looper工作原理:

    Looper是消息机制中的消息循环角色,它会不断轮询查找MessageQueue中是否有新的消息需要处理。一旦发现需要处理的新消息,就立刻取出消息交给Handler进行处理;否则就一直阻塞在那里。

    我们都知道,Looper、Thread、MessageQueue一定是绑定在一起的,因为他的作用就是轮询处理当前线程的消息队列,从它的构造器就可以看出:

     private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    

    Looper类常用方法:

    • Looper.prepare()方法:为当前线程创建一个Looper
    //判断当前线程是否有Looper,没有就创建一个Looper,并且放到当前线程的ThreadLocal中。
    //ThreadLocal:每个线程都有,并且是线程私有的,用于存储线程私有数据
    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));
        }
    • Looper.prepareMainLooper()方法:和prepare方法类似,只是它专用于给主线程创建Looper。

    • Looper.loop()方法:用于消息轮询,以下是loop方法部分重要代码解析。

     public static void loop() {
            //获取线程的Looper对象
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            //获取Looper需要轮训的消息队列
            final MessageQueue queue = me.mQueue;
            //开启无限的消息轮询(死循环),唯一跳出循环的方法就是下面msg为空,即MessageQueue调用quit方法退出 Looper。当消息队列被标记为退出状态时,next会返回null。
            for (;;) {
                //取出需要处理的消息,next是一个阻塞方法,当获取不到要处理的消息时,就会阻塞在这里。当消息队列标记为退出状态是时,不会阻塞,会返回null。接下来,判断到msg为null,就会退出循环。
                Message msg = queue.next(); // might block
                //如果上面next方法没有阻塞,msg就不可能为空。只有在Looper退出后(调用quit方法),msg才会为空。
                if (msg == null) {
                    return;
                }
    
                try {
                    //msg.target其实是一个Handler对象,这句代码就是处理消息的关键代码,将Message交给Handler去处理
                    msg.target.dispatchMessage(msg);
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                //参数复位操作
                msg.recycleUnchecked();
            }
        }
    
    • Looper.quit()方法:退出Looper方法,无论消息是否处理完,直接退出。当Looper退出后,Handler发送消息会失败。
    • Looper.quitSafely()方法:退出Looper方法,要等所有消息处理完后,才会退出。

    Handler工作原理:
    简介:Handler的主要任务是发送一条消息和获取并处理一条消息。接下来我们总结一下Handler发送消息和处理消息的一些常用方法,并且分析其实现原理。

    • Handler.sendMessage(Message msg)方法:下面是sendMessage方法调用逻辑链。
     public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }
    

    在sendMessage内部调用sendMessageDelayed方法:

     public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }

    在sendMessageDelayed内部调用sendMessageAtTime方法:

    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方法逻辑可以看出,最终还是调用enqueueMessage方法将Message插入到消息队列。接下来的逻辑就不用多说了吧。Looper轮询就该上场了…

    Handler调用链:sendMessage->sendMessageDelayed->sendMessageAtTime->Looper轮询Handler处理消息。

    • Handler.post(Runnable r)方法:post方法上面已经详细分析了,调用的也是sendMessageDelayed方法去处理Runnable中的Message。

      上面是两个重要的发送消息的方法,接下来看一下当Looper轮询检测到新消息时的处理方法。上面提到,当Looper调用loop方法轮询的时候,检测到新消息会有这么一行代码:msg.target.dispatchMessage(msg),这就是消息分发处理的方法,下面重点讲解这个方法。

      Handler.dispatchMessage(Message msg)方法:

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    其中的msg.callback实在创建Message的时候声明的,声明方式如下:
    
    //在创建Message的时候匿名创建的runable对象,一般用不到
      Message message = Message.obtain(handler, new Runnable() {
                @Override
                public void run() {
    
                }
            });
    • msg.callback不为空的情况:首先检测Message的callback是否为null,不为null就通过handleCallback方法处理消息。msg.callback是一个runable对象,就是我们通过post方法传递进来的那个runable。
    //直接调用Runable的run方法,处理逻辑。即调用上面创建Message时自己实现的run方法
    private static void handleCallback(Message message) {
            message.callback.run();
        }
    
    • msg.callback为空的情况:检测mCallback是否为null。不为null,就调用mCallback的handleMessage方法处理这条消息;当mCallback为null的时候,直接调用Handler中的handleMessage方法处理消息。其实Handler中的handleMessage方法是一个空实现的方法体,也就是说当mCallback也为null的时候,就不对这条消息进行处理了。接下来再说略显复杂的mCallback不为空的情况。

      mCallback是一个接口,看接口里面的声明方法public boolean handleMessage(Message msg),这不就是我们创建Handler的时候处理消息的方法嘛。别急,看一下在哪里给mCallback赋值的。没错,就是在Handler的构造器里面对它进行的赋值操作。因此,mCallback也是我们在创建Handler的时候自己定义的,处理消息的逻辑也就是我们自定义实现的。

     //这就是我们创建Handler对象时常用方法,这里匿名创建的Callback就是上面提到的Handler中的mCallback。
     Handler handler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    //处理Message
                    return false;
                }
            });

    总结:Handler发送消息和处理消息的方法都介绍完了,想必大家都很清楚其中的实现思路了。最后,我们总结一下整个Handler消息处理机制的流程:

    1. 通过handler.post(runable)或handler.sendMessage(msg)方法发送消息。(其中调用sendMessageDelayed->sendMessageAtTime等一系列方法)。
    2. 通过上面的方法,将Message添加至MessageQueue消息队列。
    3. Looper通过loop方法对MessageQueue进行轮询,没有新消息就阻塞。
    4. 检测到有新消息,调用dispatchMessage(msg)方法处理消息。
    5. 调用message.callback.run()或mCallback.handleMessage()方法完成对消息的处理。

    结语:希望通过本文的分析,让小伙伴们对Handler消息处理机制能有更深刻的理解。如有不对的地方,望大家指出;如果对您有帮助,望多多支持。

    展开全文
  • 进程间通信机制

    千次阅读 2016-08-31 21:13:25
     Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。 应用程序有三种方
  • 在Linux 中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下所述。 • 限制管道的大小。实际上,管道是一...
  • Android线程间通信机制

    万次阅读 2013-02-21 10:18:43
    Android线程间通信机制 当android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对...
  • VxWorks任务间通信机制

    千次阅读 2010-07-04 11:27:00
    VxWorks支持各种任务间通信机制,提供了多样的任务间通信方式,主要有如下几种: <br /> ? 共享内存,主要是数据的共享; <br /> ? 信号量,用于基本的互斥和任务同步; <br /> ? ...
  • 一张图让你彻底搞清Android线程间通信机制

    千次阅读 多人点赞 2015-03-22 12:12:51
    一张图让你彻底搞清Android线程间通信机制
  • 进程间通信机制有哪些?

    千次阅读 2016-08-14 20:09:05
    进程通信方式:  1.管道(pipe)及有名管道(named pipe):  管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程使用。进程的亲缘关系通常是指父子进程关系。  有名...
  • Windows线程间通信机制

    千次阅读 2012-12-20 14:19:30
    摘 要: 随着人们对应用...Microsoft Win32 API提供了多种进程间通信的方法,全面地阐述了这些方法的特点,并加以比较和分析,希望能给读者选择通信方法提供参考。 关键词 进程 进程通信 IPC Win32 API  1
  • 间通信机制分析

    千次阅读 2009-05-15 00:36:00
    间通信的主要目标是:充分利用硬件提供的机制,实现高效的CORE间通信;给需要CORE间通信的应用程序提供简洁高效的编程接口。根据所使用的硬件特性,核间通信可能的实现机制有:1) Mailbox中断;2) 基于共享内存...
  • 最高效的进(线)程间通信机制--eventfd

    千次阅读 2016-12-08 00:03:45
    我们常用的进程(线程)间通信机制有管道,信号,消息队列,信号量,共享内存,socket等等,其中主要作为进程(线程)间通知/等待的有管道pipe和socketpair。线程还有特别的condition。 今天来看一个liunx较新的系统...
  • Android8.0.0-r4的Binder进程间通信机制

    千次阅读 2018-03-23 07:59:47
    Android8.0.0-r4的Binder进程间通信机制 Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App...
  • Linux/windows 进程/线程间通信机制

    千次阅读 2013-08-13 22:41:57
    Linux进程间通信 linux下进程间通信的几种主要手段简介: a) 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能...
  • Binder进程间通信机制(图文解析)

    千次阅读 多人点赞 2017-12-19 01:33:16
    前言本来想洋洋洒洒写一篇形象生动的Binder原理的文章,再配上我这个灵魂画手的图画,但是再经过了1个多星期的学习后,我决定了,我自首,我这太年少了 我,太懵懂了,我没成想这Binder机制这么厉害!作为一名...
  • 最高效的进(线)程间通信机制: eventfd

    千次阅读 2017-08-10 20:38:59
    一句话总结:用于进程/...常用的进程(线程)间通信机制有管道、信号量、消息队列、信号、共享内存、socket等等,其中主要作为进程(线程)间通知/等待的有管道pipe和socket。从Linux 2.6.27版本开始增加了eventfd,主
  • 线程通信机制

    千次阅读 2012-09-03 17:08:34
    线程间通信机制: 1)互斥锁 2)条件变量 3)读写锁 4)线程信号   互斥锁:是一个二元变量,其状态为开锁,和上锁,与二元信号量不同的是,在互斥锁范围内的任何 一个线程都可以对互斥锁上锁,但是只有上...
  • Java多线程编程-(4)-线程间通信机制的介绍与使用

    千次阅读 多人点赞 2017-10-10 18:25:52
    上一篇:Java多线程编程-(1)-线程安全和锁Synchronized概念Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性Java多线程编程-(3)-线程本地ThreadLocal的介绍与使用线程间通信简介我们知道线程是操作...
  • Storm通信机制Worker间的通信经常需要通过网络跨节点进行,Storm使用ZeroMQ或Netty(0.9以后默认使用)作为进程间通信的消息框架。 Worker进程内部通信:不同worker的thread通信使用LMAX Disruptor来完成。 不同...
  •  目录 [隐藏] 1 用户进程间通信 1.1 System V IPC对象管理 1.1.1 System V IPC数据结构 1.1.1.1 (1)IPC对象属性结构kern_ipc_perm1.1.1.
  • 线程间通信之等待唤醒机制在命令式编程中,线程之间的通信机制有2种:共享内存和消息传递。在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息传递的并发模型里,...
  • 虽然Android系统是基于Linux内核,但是 它的进程间通信方式并没有完全继承自Linux,它拥有自己独特的通信方式–Binder。通过Binder我们可以进行不同应用与进程之间的相互通信以及远程方法调用。
  • Nginx有一个master进程和多个worker进程,那么master进程与worker进程间或worker进程之间是如何通信的呢,又什么时候需要进程间通信呢? 我们知道linux下的进程间通信方式主要有:管道、FIFO、套接字、消息队列、...
  • AIDL,全称名为:Android Interface Definition Language...它是安卓中一种跨进程通信的实现方式,使得不同进程不同应用之间可以保持通信。 本篇内容为基础使用篇,下面将写一个例子,来实现不同应用进程之间的通信
  • linux进程及进程同步通信机制

    千次阅读 2019-05-19 20:00:49
    套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。  (1) 信号 ---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 160,750
精华内容 64,300
关键字:

对象间的通信机制