xiaofei.library.hermes.internal.Channel.java Source code

Java tutorial

Introduction

Here is the source code for xiaofei.library.hermes.internal.Channel.java

Source

/**
 *
 * Copyright 2016 Xiaofei
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package xiaofei.library.hermes.internal;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import xiaofei.library.hermes.HermesListener;
import xiaofei.library.hermes.HermesService;
import xiaofei.library.hermes.util.CallbackManager;
import xiaofei.library.hermes.util.CodeUtils;
import xiaofei.library.hermes.util.ErrorCodes;
import xiaofei.library.hermes.util.HermesException;
import xiaofei.library.hermes.util.TypeCenter;
import xiaofei.library.hermes.wrapper.ParameterWrapper;

/**
 * Created by Xiaofei on 16/4/11.
 */
public class Channel {

    private static final String TAG = "CHANNEL";

    private static volatile Channel sInstance = null;

    private final ConcurrentHashMap<Class<? extends HermesService>, IHermesService> mHermesServices = new ConcurrentHashMap<Class<? extends HermesService>, IHermesService>();

    private final ConcurrentHashMap<Class<? extends HermesService>, HermesServiceConnection> mHermesServiceConnections = new ConcurrentHashMap<Class<? extends HermesService>, HermesServiceConnection>();

    private final ConcurrentHashMap<Class<? extends HermesService>, Boolean> mBindings = new ConcurrentHashMap<Class<? extends HermesService>, Boolean>();

    private final ConcurrentHashMap<Class<? extends HermesService>, Boolean> mBounds = new ConcurrentHashMap<Class<? extends HermesService>, Boolean>();

    private HermesListener mListener = null;

    private Handler mUiHandler = new Handler(Looper.getMainLooper());

    private static final CallbackManager CALLBACK_MANAGER = CallbackManager.getInstance();

    private static final TypeCenter TYPE_CENTER = TypeCenter.getInstance();

    //StubIHermesServiceCallback??ServiceonBindStub
    private IHermesServiceCallback mHermesServiceCallback = new IHermesServiceCallback.Stub() {

        private Object[] getParameters(ParameterWrapper[] parameterWrappers) throws HermesException {
            if (parameterWrappers == null) {
                parameterWrappers = new ParameterWrapper[0];
            }
            int length = parameterWrappers.length;
            Object[] result = new Object[length];
            for (int i = 0; i < length; ++i) {
                ParameterWrapper parameterWrapper = parameterWrappers[i];
                if (parameterWrapper == null) {
                    result[i] = null;
                } else {
                    Class<?> clazz = TYPE_CENTER.getClassType(parameterWrapper);

                    String data = parameterWrapper.getData();
                    if (data == null) {
                        result[i] = null;
                    } else {
                        result[i] = CodeUtils.decode(data, clazz);
                    }
                }
            }
            return result;
        }

        public Reply callback(CallbackMail mail) {
            final Pair<Boolean, Object> pair = CALLBACK_MANAGER.getCallback(mail.getTimeStamp(), mail.getIndex());
            if (pair == null) {
                return null;
            }
            final Object callback = pair.second;
            if (callback == null) {
                return new Reply(ErrorCodes.CALLBACK_NOT_ALIVE, "");
            }
            boolean uiThread = pair.first;
            try {
                // TODO Currently, the callback should not be annotated!
                final Method method = TYPE_CENTER.getMethod(callback.getClass(), mail.getMethod());
                final Object[] parameters = getParameters(mail.getParameters());
                Object result = null;
                Exception exception = null;
                if (uiThread) {
                    boolean isMainThread = Looper.getMainLooper() == Looper.myLooper();
                    if (isMainThread) {
                        try {
                            result = method.invoke(callback, parameters);
                        } catch (IllegalAccessException e) {
                            exception = e;
                        } catch (InvocationTargetException e) {
                            exception = e;
                        }
                    } else {
                        mUiHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    method.invoke(callback, parameters);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                        return null;
                    }
                } else {
                    try {
                        result = method.invoke(callback, parameters);
                    } catch (IllegalAccessException e) {
                        exception = e;
                    } catch (InvocationTargetException e) {
                        exception = e;
                    }
                }
                if (exception != null) {
                    exception.printStackTrace();
                    throw new HermesException(ErrorCodes.METHOD_INVOCATION_EXCEPTION,
                            "Error occurs when invoking method " + method + " on " + callback, exception);
                }
                if (result == null) {
                    return null;
                }
                return new Reply(new ParameterWrapper(result));
            } catch (HermesException e) {
                e.printStackTrace();
                return new Reply(e.getErrorCode(), e.getErrorMessage());
            }
        }

        @Override
        public void gc(List<Long> timeStamps, List<Integer> indexes) throws RemoteException {
            int size = timeStamps.size();
            for (int i = 0; i < size; ++i) {
                CALLBACK_MANAGER.removeCallback(timeStamps.get(i), indexes.get(i));
            }
        }
    };

    private Channel() {

    }

    public static Channel getInstance() {
        if (sInstance == null) {
            synchronized (Channel.class) {
                if (sInstance == null) {
                    sInstance = new Channel();
                }
            }
        }
        return sInstance;
    }

    /**
     * bind HermesService
     * @param context
     * @param packageName
     * @param service
     */
    public void bind(Context context, String packageName, Class<? extends HermesService> service) {
        HermesServiceConnection connection;
        synchronized (this) {
            if (getBound(service)) {
                return;
            }
            Boolean binding = mBindings.get(service);
            if (binding != null && binding) {
                return;
            }
            mBindings.put(service, true);
            connection = new HermesServiceConnection(service);
            mHermesServiceConnections.put(service, connection);
        }
        Intent intent;
        if (TextUtils.isEmpty(packageName)) {
            intent = new Intent(context, service);
        } else {
            intent = new Intent();
            intent.setClassName(packageName, service.getName());
        }
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    /**
     * unbind HermesService
     * @param context
     * @param service
     */
    public void unbind(Context context, Class<? extends HermesService> service) {
        synchronized (this) {
            Boolean bound = mBounds.get(service);
            if (bound != null && bound) {
                HermesServiceConnection connection = mHermesServiceConnections.get(service);
                if (connection != null) {
                    context.unbindService(connection);
                }
                mBounds.put(service, false);
            }
        }
    }

    public Reply send(Class<? extends HermesService> service, Mail mail) {
        IHermesService hermesService = mHermesServices.get(service);
        try {
            if (hermesService == null) {
                return new Reply(ErrorCodes.SERVICE_UNAVAILABLE,
                        "Service Unavailable: Check whether you have connected Hermes.");
            }
            return hermesService.send(mail);
        } catch (RemoteException e) {
            return new Reply(ErrorCodes.REMOTE_EXCEPTION,
                    "Remote Exception: Check whether " + "the process you are communicating with is still alive.");
        }
    }

    public void gc(Class<? extends HermesService> service, List<Long> timeStamps) {
        IHermesService hermesService = mHermesServices.get(service);
        if (hermesService == null) {
            Log.e(TAG,
                    "Service Unavailable: Check whether you have disconnected the service before a process dies.");
        } else {
            try {
                hermesService.gc(timeStamps);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public boolean getBound(Class<? extends HermesService> service) {
        Boolean bound = mBounds.get(service);
        return bound != null && bound;
    }

    public void setHermesListener(HermesListener listener) {
        mListener = listener;
    }

    public boolean isConnected(Class<? extends HermesService> service) {
        IHermesService hermesService = mHermesServices.get(service);
        return hermesService != null && hermesService.asBinder().pingBinder();
    }

    private class HermesServiceConnection implements ServiceConnection {

        private Class<? extends HermesService> mClass;

        HermesServiceConnection(Class<? extends HermesService> service) {
            mClass = service;
        }

        public void onServiceConnected(ComponentName className, IBinder service) {
            synchronized (Channel.this) {
                mBounds.put(mClass, true);
                mBindings.put(mClass, false);
                IHermesService hermesService = IHermesService.Stub.asInterface(service);
                mHermesServices.put(mClass, hermesService);
                try {
                    hermesService.register(mHermesServiceCallback, Process.myPid());
                } catch (RemoteException e) {
                    e.printStackTrace();
                    Log.e(TAG, "Remote Exception: Check whether "
                            + "the process you are communicating with is still alive.");
                    return;
                }
            }
            if (mListener != null) {
                mListener.onHermesConnected(mClass);
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            synchronized (Channel.this) {
                mHermesServices.remove(mClass);
                mBounds.put(mClass, false);
                mBindings.put(mClass, false);
            }
            if (mListener != null) {
                mListener.onHermesDisconnected(mClass);
            }
        }
    }
}