android IPC通信(下)-AIDL

  android IPC通信(上)-sharedUserId&&Messenger
  android IPC通信(中)-ContentProvider&&Socket
  这篇我们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,而且Messenger,ContentProvider和AIDL的最底层都是使用的Binder。

Binder

  直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager,WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
  还有两点需要提到,第一点就是当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现,因为他已经运行在一个线程中了。下图为Binder的工作机制图:
  这里写图片描述
  可以看到Client客户端会block直到方法返回。Binder的介绍就到此为止了,需要详细了解Binder的可以看看老罗的文章:
  http://blog.csdn.net/luoshengyang/article/details/6618363

AIDL

 AIDL的全称是Android Interface definition language,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口,用处当然就是用来进程间的通信和方法调用了(我在IPC通信上篇中介绍过也可以使用Messenger加上反射机制来进行跨应用的方法调用,但是前提是让两个应用在一个进程中,局限性比AIDL大)。先介绍一下AIDL进程间通信的流程:

  1. AIDL接口的创建
  2. AIDL文件中,并不是所有的数据类型都是可以使用的,它支持的数据类型有:
  • 基本数据类型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,而且list中的元素也必须是AIDL支持的类型
  • Map:只支持HashMap,里面的key和value也必须是AIDL支持的类型
  • Parceable:所有实现了Parceable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用,所以IBinder类型也是支持的。
  • 服务端
  • 服务端首先要创建一个Service用来监听客户端的请求,然后将在对应AIDL文件中声明的接口实现,并且通过onbind函数返回相应IBinder对象即可。
  • 客户端
  • 客户端所要做的事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。  更多内容也可以去看google文档,介绍完流程之后,紧接着来介绍一下demo中的相关代码。

    aidl文件

      第一步创建几个相关的AIDL文件,特别需要注意的是在AS中,先要在app_name/src/main/文件夹下创建一个aidl文件夹,接下来在该文件夹下去创建相关的package用来放置这些AIDL文件,基本结构如下图所示:
      这里写图片描述
    不这么做是无法使用的。接着我们就来仔细分析这几个AIDL文件:

    // IWeatherManager.aidl
    package com.android.aidl;
    import com.android.aidl.Weather;
    import com.android.aidl.listener.IWeatherChangeListener;
    
    interface IWeatherManager {
        List<Weather> getWeather();
        void addWeather(in Weather weather);
        void addListener(in IWeatherChangeListener listener);
        void removeListener(in IWeatherChangeListener listener);
    }

      这个IWeatherManager.aidl文件是连接客户端和服务端的核心文件,我们可以看到这个aidl文件中需要引用两个类:Weather和IWeatherChangeListener,看看这两个aidl文件的代码:

    //Weather.aidl
    package com.android.aidl;
    parcelable Weather;
    // IWeatherChangeListener.aidl
    package com.android.aidl.listener;
    import com.android.aidl.Weather;
    
    interface IWeatherChangeListener {
        void onWeatherChange(in Weather newWeather);
    }

      详细介绍一下这几个文件的要点:第一点是如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在IWeatherManager.aidl文件中用到了Weather这个Parcelable类,所以我们必须要创建Weather.aidl文件,要不然只有一个Weather.java文件是无法识别的,并且非常重要的是Weather.aidl和Weather.java两个文件的包名必须要一致,比如demo中的都为com.android.aidl,不一致也会导致Weather类无法识别;第二点是AIDL中除了基本数据类型,其他类型的参数必须标上方向:in,out或者inout, in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的;第三点是AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。
      在这个demo中,我们仍然是在一个应用中创建两个进程进行通信,和在两个应用中的两个进程之间进行通信是很类似的,差异方面就以这个demo来说第一个需要在两个应用中的app_name/src/main/文件夹下都创建一个aidl文件夹,然后将三个aidl文件整体拷贝进来,当然要保证两个应用的package名字com.android.aidl一样;第二个还有Weather.java文件也必须在两个应用中的com.android.aidl(就是要和Weather.aidl的package名字一致)包下面,做到这两点就可以了。一个工程和两个工程的多进程本质是一样的,有兴趣的可以自己试试。

    java文件

    Parcelable实体类

      我们来看看demo中Weather.java类的代码:

    public class Weather implements Parcelable{
        public String cityName;
        public double temperature;
        public double humidity;
        public AllWeather weather;
    
        protected Weather(Parcel in) {
            temperature = in.readDouble();
            humidity = in.readDouble();
            //使用该方式来写入枚举
            weather = AllWeather.values()[in.readInt()];
            cityName = in.readString();
        }
    
        public Weather() {
    
        }
    
        public static final Creator<Weather> CREATOR = new Creator<Weather>() {
            @Override
            public Weather createFromParcel(Parcel in) {
                return new Weather(in);
            }
    
            @Override
            public Weather[] newArray(int size) {
                return new Weather[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeDouble(temperature);
            dest.writeDouble(humidity);
            dest.writeInt(weather.ordinal());
            dest.writeString(cityName);
        }
    
        public enum AllWeather{
            sunny,cloudy,rain,snowy
        }
    }

      代码很简单,就是实现Parcelable接口即可,唯一的难点就是enum枚举在多客户端之间的处理了,处理方法就是使用ordinal()函数将枚举转换成int类型,通着这个int值,就可以找到枚举变量在枚举类中的位置,也就可以知道原始值了。

    服务端

      接着就是服务端的代码实现了:

    public class WeatherManagerService extends Service{
    
        //支持并发读写的list
        public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
        public RemoteCallbackList<IWeatherChangeListener> listeners = new RemoteCallbackList<>();
    
        @Override
        public void onCreate() {
            super.onCreate();
            Weather nanshan = new Weather();
            nanshan.cityName = "南山";
            nanshan.temperature = 20.5;
            nanshan.humidity = 45;
            nanshan.weather = Weather.AllWeather.cloudy;
    
            Weather futian = new Weather();
            futian.cityName = "福田";
            futian.temperature = 21.5;
            futian.humidity = 48;
            futian.weather = Weather.AllWeather.rain;
    
            weathers.add(nanshan);
            weathers.add(futian);
        }
    
        private Binder mBinder = new IWeatherManager.Stub() {
            @Override
            public List<Weather> getWeather() throws RemoteException {
                L.i("server returns all of the weathers");
                return weathers;
            }
    
            @Override
            public void addWeather(Weather weather) throws RemoteException {
                weathers.add(weather);
                L.i("server add new Weather:" + weather.cityName);
    
                int N = listeners.beginBroadcast();
                for (int i=0; i<N; i++){
                    IWeatherChangeListener listener = listeners.getBroadcastItem(i);
                    listener.onWeatherChange(weather);
                }
                L.i("server notify the listener that weathers have been changed");
                listeners.finishBroadcast();
            }
    
            @Override
            public void addListener(IWeatherChangeListener listener) throws RemoteException {
                L.i("server adding listener");
                listeners.register(listener);
            }
    
            @Override
            public void removeListener(IWeatherChangeListener listener) throws RemoteException {
                L.i("server removing listener");
                listeners.unregister(listener);
            }
    
            @Override
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
                //检测客户端是否声明权限
                if (permission == PackageManager.PERMISSION_DENIED){
                    L.e("permission denied");
                    return false;
                }
                L.i("permission granted");
    
                String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
                if (packages != null && packages.length > 0){
                    String packageName = packages[0];
                    if (!packageName.startsWith("com.android")){
                        L.e("package name not accept");
                        return false;
                    }
                    L.i("package name accept");
                }
                return super.onTransact(code, data, reply, flags);
            }
        };
    
        @Override
        public IBinder onBind(Intent intent) {
    //        int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
    //        //检测客户端是否声明权限
    //        if (permission == PackageManager.PERMISSION_DENIED){
    //            L.e("permission denied");
    //            return null;
    //        }
            return mBinder;
        }
    }

      服务端的实现比较复杂,我们一步步来分析:

    1. 服务端当然是个Service,在该Service中我们需要新建一个binder对象,这个binder对象是一个由IWeatherManager.aidl生成的IWeatherManager接口中的内部Stub类的对象,该对象需要实现4个接口中的方法。然后在onBind函数返回这个binder对象即可。
    2. 为了支持多进程的并发读写,我们需要使用CopyOnWriteArrayList而不是普通list,类似的还有ConcurrentHashMap。
    3. 如果需要为这个Service增加访问的权限,有三个方法来实现:
      • 先使用permission标签定义一个permission(详情看这篇博客),然后在manifest文件中的服务端service标签中添加android:permission=”yourPermissionName”即可。
      • 同样的先声明一个permission,接着在Service的onBind函数中,通过checkCallingPermission函数检测调用者是否使用了该声明的权限,如果没有就直接返回null。
      • 在onTransact函数中进行检测,和onBind中的检测一样,不通过返回false,而且在该函数中还可以检测调用者的package name,在demo中如果调用者应用的包名不是以com.android开头,就会拒绝访问。简单介绍一下onTransact函数,该函数运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理,如果此方法返回false,那么客户端的请求会失败,所以在这个方法中检测权限也是可以的。
    4. 如何为服务端添加监听器?我们知道客户端的listener对象经过parcelable之后到服务端的对象并不是同一个对象,所以如果客户端想要解注册一个listener,调用服务端removeListener函数并传入一个listener参数,但是这个listener对象经过parcelable之后并不是原来的那个对象,服务端无法处理,所以为了应对这种情况,系统专门提供了用于跨进程删除listener的接口RemoteCallbackList。RemoteCallbackList是一个泛型,因为继承自IInterface接口,所以支持管理任意的AIDL接口。为什么RemoteCallbackList类就可以识别parcelable之后的对象呢?先来看看RemoteCallbackList的实现,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型:
      ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>()
      其中Callback中封装了真正的远程listener。当客户端注册listener的时候,他会把这个listener的信息存入mCallbacks中,其中的key和value分别通过下面的方式获得:
      IBinder binder = callback.asBinder();
      Callback cb = new Callback(callback, cookie);
      虽然说多次跨进程传输客户端的同一个对象在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个,所以使用RemoteCallbackList就能够成功的删除指定listener。

    客户端

      看看客户端的代码:

    public class ClientActivity extends BaseActivity implements View.OnClickListener{
    
        private ServiceConnection serviceConnection = null;
        private IBinder.DeathRecipient deathRecipient = null;
        private IWeatherChangeListener listener = null;
        private IWeatherManager weatherManager;
        private TextView tv_content;
        private TextView tv_add;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_client);
            findViewById(R.id.btn_add).setOnClickListener(this);
            findViewById(R.id.btn_query).setOnClickListener(this);
            findViewById(R.id.btn_remove_listener).setOnClickListener(this);
            tv_content = (TextView) findViewById(R.id.tv_content);
            tv_add = (TextView) findViewById(R.id.tv_add);
            listener = new IWeatherChangeListener.Stub(){
    
                @Override
                public void onWeatherChange(Weather newWeather) throws RemoteException {
                    L.i("client has been notified that "+newWeather.cityName+" has been added");
                    tv_add.setText(newWeather.cityName + "has been added");
                }
            };
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    weatherManager = IWeatherManager.Stub.asInterface(service);
                    try {
                        weatherManager.asBinder().linkToDeath(deathRecipient, 0);
                        weatherManager.addListener(listener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    weatherManager = null;
                }
            };
    
            deathRecipient = new IBinder.DeathRecipient() {
                @Override
                public void binderDied() {
                    //移出之前的死亡容器
                    weatherManager.asBinder().unlinkToDeath(deathRecipient, 0);
                    weatherManager = null;
    
                    //重新连接
                    bindServer();
                }
            };
            bindServer();
        }
    
        private void bindServer(){
            Intent intent = new Intent(this, WeatherManagerService.class);
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.btn_query){
                try {
                    //调用远程服务端接口时,客户端进程会挂起,勿在主线程中调用耗时远程操作
                    L.i("client is getting weather");
                    List<Weather> weathers = weatherManager.getWeather();
                    L.i("client has gotten weather");
                    StringBuilder sb = new StringBuilder();
                    for (Weather weather : weathers){
                        sb.append(weather.cityName).append("\n");
                        sb.append("humidity:").append(weather.humidity)
                            .append("temperature").append(weather.temperature)
                            .append("weather").append(weather.weather).append("\n");
                    }
                    tv_content.setText(sb);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }else if (v.getId() == R.id.btn_add){
                Weather weather = new Weather();
                weather.weather = Weather.AllWeather.cloudy;
                weather.humidity = 25.5;
                weather.temperature = 19.5;
                weather.cityName = "罗湖";
                try {
                    //调用远程服务端接口时,客户端进程会挂起,勿在主线程中调用耗时远程操作
                    L.i("client is adding weather " + weather.cityName);
                    weatherManager.addWeather(weather);
                    L.i("client has added weather " + weather.cityName);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }else if (v.getId() == R.id.btn_remove_listener){
                try {
                    weatherManager.removeListener(listener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(serviceConnection);
            try {
                weatherManager.asBinder().linkToDeath(deathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

      客户端逻辑很简单,bindService绑定服务端之后,将服务端传过来的IBinder对象通过asInterface方法转换成AIDL接口,然后就能通过这个接口去调用服务端的远程方法了。而且在客户端还能够注册死亡代理,新建一个DeathRecipient对象,并且使用Binder的linkToDeath注册该对象,当Binder死亡时,我们就会收到通知,unlinkToDeath函数可以解注册该死亡代理。
      还有非常重要的几点需要说明:客户端调用服务端方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,所以如果知道服务端的一个方法是耗时的,就要避免在客户端的UI线程中去调用该远程方法。由于onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在这两个函数中调用耗时方法。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法可以执行大量的耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。
      关于服务端和客户端的方法分别执行在那个进程和线程中以及它们执行的先后顺序,我们先来看看log日志的输出:

    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7035] 1.addListener(line:72): server adding listener
    
    //***client add click
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:115): client is adding weather 罗湖
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7036] 1.addWeather(line:59): server add new Weather:罗湖
    I/[PID:28502](28502): [TID:1] 1.onWeatherChange(line:47): client has been notified that 罗湖 has been added
    I/[PID:28533](28533): [TID:7036] 1.addWeather(line:66): server has notified the listener that weathers have been changed
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:117): client has added weather 罗湖
    
    //***client remove listener click
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7035] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7035] 1.removeListener(line:78): server removing listener
    
    //***client get click
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:93): client is getting weather
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:90): permission granted
    I/[PID:28533](28533): [TID:7036] 1.onTransact(line:99): package name accept
    I/[PID:28533](28533): [TID:7036] 1.getWeather(line:52): server returns all of the weathers
    I/[PID:28502](28502): [TID:1] ClientActivity.onClick(line:95): client has gotten weather

      PID:28502为客户端进程,PID:28533为服务端进程,TID:1为UI主线程,TID:7036为Binder线程。看看log打印的顺序基本就能够明白方法的执行进程,线程和客户端的阻塞情况了。
      源码下载:https://github.com/zhaozepeng/IPC-demo/tree/master/AIDL

    BinderPool

      上面差不多就把AIDL的用法详细介绍完了,但是有的时候我们可能需要不止一个业务模块,也就是不单单需要一个天气模块,我们还需要一个计算温度平均值的模块(虽然可以写在一个模块中,但是我们还是假设要用两个模块吧~),是不是需要为每个模块都单独建立一个Service呢?当然不是,会很耗资源的好吗,解决方法就是先为每一个模块建立一个单独的aidl文件,最后再建立一个整体的aidl文件用来管理这些单独的aidl。
      看看这三个文件,IWeatherManager.aidl,IComputerManager.aidl和IBinderPoolManager.aidl:

    // IWeatherManager.aidl
    package com.android.binderpool;
    import com.android.binderpool.Weather;
    
    interface IWeatherManager {
        List<Weather> getWeather();
        void addWeather(in Weather weather);
    }
    // IComputerManager.aidl
    package com.android.binderpool;
    import com.android.binderpool.Weather;
    
    interface IComputerManager {
        double computeAverageTemperature(in List<Weather> weathers);
    }
    // IBinderPoolManager.aidl
    package com.android.binderpool;
    
    interface IBinderPoolManager {
        IBinder queryCode(int code);
    }

    IBinderPoolManager.aidl文件用来统一管理所有的AIDL接口,queryCode函数通过code值来确定需要返回给客户端的IBinder对象。
      来看看服务端的代码的变动:

    public class BinderPoolService extends Service{
        public static final int CODE_WEATHER = 1;
        public static final int CODE_COMPUTER = 2;
    
        private IBinderPoolManager iBinderPoolManager;
    
        //支持并发读写的list
        public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
    
        @Override
        public void onCreate() {
            super.onCreate();
            Weather nanshan = new Weather();
            nanshan.cityName = "南山";
            nanshan.temperature = 20.5;
            nanshan.humidity = 45;
            nanshan.weather = Weather.AllWeather.cloudy;
    
            Weather futian = new Weather();
            futian.cityName = "福田";
            futian.temperature = 21.5;
            futian.humidity = 48;
            futian.weather = Weather.AllWeather.rain;
    
            weathers.add(nanshan);
            weathers.add(futian);
            iBinderPoolManager = new IBinderPoolManager.Stub(){
                @Override
                public IBinder queryCode(int code) throws RemoteException {
                    switch (code){
                        case CODE_WEATHER:
                            return new IWeatherManager.Stub(){
    
                                @Override
                                public List<Weather> getWeather() throws RemoteException {
                                    return weathers;
                                }
    
                                @Override
                                public void addWeather(Weather weather) throws RemoteException {
                                    weathers.add(weather);
                                }
                            };
                        case CODE_COMPUTER:
                            return new IComputerManager.Stub() {
                                @Override
                                public double computeAverageTemperature(List<Weather> weathers) throws RemoteException {
                                    double sum = 0;
                                    for (int i=0; i<weathers.size(); i++){
                                        sum += weathers.get(i).temperature;
                                    }
                                    return sum/weathers.size();
                                }
                            };
                        default:
                            return null;
                    }
                }
            };
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return iBinderPoolManager.asBinder();
        }
    }

      根据code的不同返回不同的IBinder对象,这样在客户端中就能够获取对应AIDL接口的IBinder对象,最终就能在客户端调用不同AIDL模块中的方法。客户端代码很简单,在这里就不介绍了,感兴趣的可以去看看源码:
      https://github.com/zhaozepeng/IPC-demo/tree/master/BinderPool
      关于IPC相关知识的介绍就到这了,如果有什么疑问,大家可以多多交流啊,谢谢~

    本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

    Unity AssetBundles 使用指南 - 2015-12-18 19:12:02

    0x00:简介 AssetBundles 是Unity使用的一种资源格式,AssetBundles资源可以在不同项目交叉单独使用,Unity中主要用AssetBundles使资源和可执行文件分离。 0x01:生成AssetBundles AssetBundle可以调用Unity接口: BuildPipeLine .BuildAssetBundle (Object mainAsset, Object[] assets, string pathName, BuildAssetBundleOptions ass
    第七章、策略模式 通常如果一个问题有多个解决方案时,最简单的就是利用if-else或者switch-case方式根据不同的情景选择不同的解决方案,但是这样耦合性太高 、代码臃肿、难以维护等。这时就可以使用策略模式来解决。 1.定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 2.使用场景 1.针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。 2.需要安全地封装多种同一类型的操作时。 3.出现同一抽象类有多个子类,而又需

    百度推送代码备份 - 2015-12-18 17:12:11

    首先查看百度推送开发者文档,这里主要是用Java实现。 http://push.biadu.com Java项目jar 引用,这里使用maven管理jar 包。 HomePage : https://github.com/featherfly/sorm.git dependency groupId cn.featherfly / groupId artifactId bccs-api / artifactId version 3.0.1 / version / dependency 推送工具类封装,这里直
    Xcode 7.0 官方免费的真机开发 太阳火神的美丽人生 ( http://blog.csdn.net/opengl_es ) 本文遵循“ 署名-非商业用途-保持一致 ”创作公用协议 转载请保留此句: 太阳火神的美丽人生 -  本博客专注于  敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino , 否则,出自本博客的文章拒绝转载或再转载,谢谢合作。 苹果开发需要跟进的另一篇文档:What's New in Xcode 关于免费的真机开发描述如下:

    Android Context 到底是什么? - 2015-12-17 22:12:03

    什么是Context? 一个Context意味着一个场景,一个场景就是我们和软件进行交互的一个过程。比如当你使用微信的时候,场景包括聊天界面、通讯录、朋友圈,以及背后的一些数据。 那么从程序的角度来看,Context是什么?其实一个Activity就是一个Context,一个Service也是一个Context。 一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景,这就像一个助理,他可能需要接待客人,可能还要打印文件,还可能接听电话,而这些就称之为不同的场景,助理可称之为一个应用程
    美颜包含磨皮、美白、瘦脸等效果,其中磨皮算法在很多博客中均有介绍 例如: 双指数边缘平滑滤波器用于磨皮算法的尝试 选择性模糊及其算法的实现 基于局部均方差相关信息的图像去噪及其在实时磨皮美容算法中的应用 导向滤波磨皮 递归双边滤波磨皮 以上博客均有相关代码/公式,经试验若选取合适参数均有不错的效果,可惜水平有限尚未在shader中实现不卡顿的实时效果~ 观察美图秀秀和华为自带相机等相机APP,发现实时美颜效果均不如PC端和手机端后处理,可能在这一领域目前解决办法不多或者需求不高吧。 下面就探讨简单的美颜滤

    闪屏(Splash) - 2015-12-17 19:12:52

    好久没弄ReactNative了, 写个如何实现闪屏(Splash)的文章吧. 注意: (1) 如何切换页面. (2) 如何使用计时器TimerMixin. (3) 如何使用动画效果. (4) 如何加载Android的项目资源(图片). 1. 准备 新建项目, 添加主模块 index.android.js . /* @flow */ /** * 测试 * @author wangchenlong */ 'use strict' ; var React = require ( 'react-native'
    1. Local storage背景     cookie弊端:同域内http请求都会带cookie,增加带宽和流量;有个数和大小限制(约4K)。     在HTML5中,本地存储是一个window的属性,包括localStorage和sessionStorage,从名字应该可以很清楚的辨认二者的区别,前者是一直存在本地的,后者只是伴随着session,窗口一旦关闭就没了。二者用法完全相同。
    MSM8909+Android5.1.1通过USB连接XP系统无法识别问题   遇到此问题,可以安装应用宝、腾讯手机管家等手机管理软件,应该可正常让Android设备和XP系统正常通信。喜欢折腾的朋友可以看下面的实践总结。   USB连接方式: 图1   1.     媒体设备(MTP) 图2 自动安装软件提示安装失败,参考解决此问题 http://www.apk3.com/androidnews/html/1577.html (1)  安装XP完整新版的Windows Media Player 11 此
    苹果官方有一句话说的非常好: 当控制器的view互为父子关系,那么控制器最好也互为父子关系 我之前有一篇博客说 控制器view的显示 里边我说了一个很严重的问题,就是当控制的view还在,但是控制器不在了,造成了数据无法显示的问题,所以我们就要想办法保住控制器的命。那么我们今天继续来看一下,如何保住控制器的命。 今天我们来用屏幕旋转的一个案例来说明一个问题:当控制器的view互为父子关系的时候,控制器不是父子关系时,会出现什么严重的问题。 看一个案例,在ipad开发中,屏幕旋转是经常要发生的事情,因为屏幕