/*
 * Copyright (C) 2005-2017 Qihoo 360 Inc.
 *
 * 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 com.qihoo360.replugin;

import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;

import com.qihoo360.i.PluginFactory;
import com.qihoo360.i.Factory2;
import com.qihoo360.i.IPluginManager;
import com.qihoo360.utils.CertUtils;
import com.qihoo360.loader.RePluginOS;
import com.qihoo360.loader.PluginMgrFacade;
import com.qihoo360.loader.PluginStatusController;
import com.qihoo360.mobilesafe.api.AppVar;
import com.qihoo360.mobilesafe.api.Tasks;
import com.qihoo360.mobilesafe.core.BuildConfig;
import com.qihoo360.mobilesafe.svcmanager.QihooServiceManager;
import com.qihoo360.replugin.base.IPC;
import com.qihoo360.replugin.component.ComponentList;
import com.qihoo360.replugin.component.app.PluginApplicationClient;
import com.qihoo360.replugin.debugger.DebuggerReceivers;
import com.qihoo360.replugin.helper.HostConfigHelper;
import com.qihoo360.replugin.helper.LogDebug;
import com.qihoo360.replugin.helper.LogRelease;
import com.qihoo360.replugin.model.PluginInfo;
import com.qihoo360.replugin.packages.PluginFastInstallProviderProxy;
import com.qihoo360.replugin.packages.PluginInfoUpdater;
import com.qihoo360.replugin.packages.PluginManagerProxy;
import com.qihoo360.replugin.packages.PluginRunningList;

import java.io.File;
import java.util.List;

import static com.qihoo360.replugin.helper.LogDebug.LOG;

/**
 * RePlugin的对外入口类 <p>
 * 宿主App可直接调用此类中的方法,来使用插件化的几乎全部的逻辑。
 *
 * @author RePlugin Team
 */

public class RePlugin {

    private static final String TAG = "RePlugin";

    /**
     * 插件名为“宿主”。这样插件可以直接通过一些方法来使用“宿主”的接口
     */
    public static final String PLUGIN_NAME_MAIN = "main";

    /**
     * 表示目标进程根据实际情况自动调配
     */
    public static final String PROCESS_AUTO = "" + IPluginManager.PROCESS_AUTO;

    /**
     * 表示目标为UI进程
     */
    public static final String PROCESS_UI = "" + IPluginManager.PROCESS_UI;

    /**
     * 表示目标为常驻进程(名字可变,见BuildConfig内字段)
     */
    public static final String PROCESS_PERSIST = "" + IPluginManager.PROCESS_PERSIST;

    private static RePluginConfig sConfig;

    /**
     * 安装此插件 <p>
     * 注意: <p>
     * 1、这里只将APK移动(或复制)到“插件路径”下,不释放优化后的Dex和Native库,不会加载插件 <p>
     * 2、此方法是【同步】的,耗时较少
     *
     * @param path 插件安装的地址。必须是“绝对路径”。通常可以用context.getFilesDir()来做
     * @return 安装成功的插件信息,外界可直接读取
     * @since 2.0.0 (1.x版本为installDelayed)
     */
    public static PluginInfo install(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new IllegalArgumentException();
        }

        // 判断文件合法性
        File file = new File(path);
        if (!file.exists()) {
            if (LogDebug.LOG) {
                LogDebug.e(TAG, "plugin install: File not exists. path=" + path);
            }
            return null;
        } else if (!file.isFile()) {
            if (LogDebug.LOG) {
                LogDebug.e(TAG, "plugin install: Not a valid file. path=" + path);
            }
            return null;
        }


        return RePluginOS.pluginDownloaded(path);
    }

    /**
     * 卸载此插件 <p>
     * 注意: <p>
     * 此卸载功能只针对"纯APK"插件方案 <p>
     *
     * @param pluginName 待卸载插件名字
     * @return 插件卸载是否成功
     * @since 2.1.0
     */
    public static boolean uninstall(String pluginName) {
        if (TextUtils.isEmpty(pluginName)) {
            throw new IllegalArgumentException();
        }
        return RePluginOS.pluginUninstall(pluginName);
    }

    /**
     * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。 <p>
     * 具体用法可参见preload(PluginInfo)的说明
     *
     * @param pluginName 要加载的插件名
     * @return 预加载是否成功
     * @see #preload(PluginInfo)
     * @since 2.0.0
     */
    public static boolean preload(String pluginName) {
        PluginInfo pi = getPluginInfo(pluginName);
        if (pi == null) {
            // 插件不存在,无法加载Dex
            if (LogDebug.LOG) {
                LogDebug.e(TAG, "preload: Plugin not found! pn=" + pluginName);
            }
            return false;
        }
        return preload(pi);
    }

    /**
     * 预加载此插件。此方法会立即释放优化后的Dex和Native库,但不会运行插件代码。 <p>
     * 使用场景:在“安装”完成后“提前释放Dex”(时间算在“安装过程”中)。这样下次启动插件时则速度飞快 <p>
     * 注意: <p>
     * 1、该方法非必须调用(见“使用场景”)。换言之,只要涉及到插件加载,就会自动完成preload操作,无需开发者关心 <p>
     * 2、Dex和Native库会占用大量的“内部存储空间”。故除非插件是“确定要用的”,否则不必在安装完成后立即调用此方法 <p>
     * 3、该方法为【同步】调用,且耗时较久(尤其是dex2oat的过程),建议在线程中使用
     *
     * @param pi 要加载的插件信息
     * @return 预加载是否成功
     * @see #install(String)
     * @since 2.0.0
     */
    public static boolean preload(PluginInfo pi) {
        if (pi == null) {
            return false;
        }

        // 借助“UI进程”来快速释放Dex(见PluginFastInstallProviderProxy的说明)
        return PluginFastInstallProviderProxy.install(RePluginInternal.getAppContext(), pi);
    }

    /**
     * 是否启用调试器,Debug阶段建议开启,Release阶段建议关闭,默认为关闭状态
     *
     * @param context Context对象
     * @param enable  true=开启
     * @return 是否执行成功
     * @since 2.0.0
     */
    public static boolean enableDebugger(Context context, boolean enable) {
        if ((null != context) && enable) {
            DebuggerReceivers debuggerReceivers = new DebuggerReceivers();
            debuggerReceivers.registerReceivers(context);
        }
        return true;
    }

    /**
     * 开启一个插件的Activity <p>
     * 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象
     *
     * @param context Context对象
     * @param intent  要打开Activity的Intent,其中ComponentName的Key必须为插件名
     * @return 插件Activity是否被成功打开?
     * FIXME 是否需要Exception来做?
     * @see #createIntent(String, String)
     * @since 1.0.0
     */
    public static boolean startActivity(Context context, Intent intent) {
        // TODO 先用旧的开启Activity方案,以后再优化
        ComponentName cn = intent.getComponent();
        if (cn == null) {
            // TODO 需要支持Action方案
            return false;
        }
        String plugin = cn.getPackageName();
        String cls = cn.getClassName();
        return PluginFactory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
    }

    /**
     * 开启一个插件的Activity,无需调用createIntent或设置ComponentName来修改Intent
     *
     * @param context    Context对象
     * @param intent     要打开Activity的Intent,其中ComponentName的Key必须为插件名
     * @param pluginName 插件名。稍后会填充到Intent中
     * @param activity   插件的Activity。稍后会填充到Intent中
     * @see #startActivity(Context, Intent)
     * @since 1.0.0
     */
    public static boolean startActivity(Context context, Intent intent, String pluginName, String activity) {
        // TODO 先用旧的开启Activity方案,以后再优化
        return PluginFactory.startActivity(context, intent, pluginName, activity, IPluginManager.PROCESS_AUTO);
    }

    /**
     * 通过 forResult 方式启动一个插件的 Activity
     *
     * @param activity    源 Activity
     * @param intent      要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
     * @param requestCode 请求码
     * @see #startActivityForResult(Activity, Intent, int, Bundle)
     * @since 2.1.3
     */
    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode) {
        return PluginFactory.startActivityForResult(activity, intent, requestCode, null);
    }

    /**
     * 通过 forResult 方式启动一个插件的 Activity
     *
     * @param activity    源 Activity
     * @param intent      要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
     * @param requestCode 请求码
     * @param options     附加的数据
     * @see #startActivityForResult(Activity, Intent, int, Bundle)
     * @since 2.1.3
     */
    public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
        return PluginFactory.startActivityForResult(activity, intent, requestCode, options);
    }

    /**
     * 创建一个用来定向到插件组件的Intent <p>
     * <p>
     * 推荐用法: <p>
     * <code>
     * Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity");
     * </code> <p>
     * 当然,也可以用标准的Android创建方法: <p>
     * <code>
     * Intent in = new Intent(); <p>
     * in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"));
     * </code>
     *
     * @param pluginName 插件名
     * @param cls        目标全名
     * @return 可以被RePlugin识别的Intent
     * @since 1.0.0
     */
    public static Intent createIntent(String pluginName, String cls) {
        Intent in = new Intent();
        in.setComponent(createComponentName(pluginName, cls));
        return in;
    }

    /**
     * 创建一个用来定向到插件组件的ComponentName,其Key为插件名,Value为目标组件的类全名
     *
     * @param pluginName 插件名
     * @param cls        目标组件全名
     * @return 一个修改过的ComponentName对象
     * @since 1.0.0
     */
    public static ComponentName createComponentName(String pluginName, String cls) {
        return new ComponentName(pluginName, cls);
    }

    /**
     * 添加允许插件使用的签名指纹。一旦添加进来,则通过该签名制作的插件将允许被执行,否则将不能被执行 <p>
     * 注意:请不要从Prefs中“缓存”签名信息,防止别他人篡改后,导致恶意插件被校验通过
     *
     * @param sign 签名指纹
     * @since 1.0.0
     */
    public static void addCertSignature(String sign) {
        if (TextUtils.isEmpty(sign)) {
            throw new IllegalArgumentException("arg is null");
        }
        CertUtils.SIGNATURES.add(sign.toUpperCase());
    }

    /**
     * 是否使用Dev版AAR?可支持一些"调试特性",但该AAR【千万不要用于发布环境】 <p>
     * Dev版的AAR可支持如下特性: <p>
     * 1、插件签名不正确时仍允许被安装进来,这样利于调试(发布环境上则容易导致严重安全隐患) <p>
     * 2、可以打出一些完整的日志(发布环境上则容易被逆向,进而对框架稳定性、私密性造成严重影响)
     *
     * @return 是否使用Dev版的AAR?
     * @since 1.0.0
     */
    public static boolean isForDev() {
        return RePluginInternal.FOR_DEV;
    }

    /**
     * 获取SDK的版本信息
     *
     * @return SDK的版本,如2.0.0等
     * @since 2.0.0
     */
    public static String getSDKVersion() {
        return BuildConfig.VERSION_NAME;
    }

    /**
     * 获取插件的组件列表(ComponentList) <p>
     * 注意:这里会尝试安装插件,但不会加载资源和代码,因此会有一些耗时的情况 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder
     *
     * @param pluginName 要获取的插件名
     * @return ComponentList对象
     * @see ComponentList
     * @since 1.0.0
     */
    public static ComponentList fetchComponentList(String pluginName) {
        return PluginFactory.queryPluginComponentList(pluginName);
    }

    /**
     * 加载插件,并获取插件的包信息 <p>
     * 注意:这里会尝试加载插件,并释放其Jar包。但不会读取资源,也不会释放oat/odex <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo(This) < Resources < ClassLoader < Context < Binder
     *
     * @param pluginName 插件名
     * @return PackageInfo对象
     * @see PackageInfo
     * @since 1.0.0
     */
    public static PackageInfo fetchPackageInfo(String pluginName) {
        return PluginFactory.queryPluginPackageInfo(pluginName);
    }

    /**
     * 加载插件,并获取插件的资源信息 <p>
     * 注意:这里会尝试安装插件,并释放其Jar包,读取资源,但不会释放oat/odex。 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources(This) < ClassLoader < Context < Binder
     *
     * @param pluginName 插件名
     * @return Resources对象
     * @see Resources
     * @since 1.0.0
     */
    public static Resources fetchResources(String pluginName) {
        return PluginFactory.queryPluginResouces(pluginName);
    }

    /**
     * 加载插件,并获取插件自身的ClassLoader对象,以调用插件内部的类 <p>
     * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader(This) < Context < Binder
     *
     * @param pluginName 插件名
     * @return 插件的ClassLoader对象
     * @since 1.0.0
     */
    public static ClassLoader fetchClassLoader(String pluginName) {
        return PluginFactory.queryPluginClassLoader(pluginName);
    }

    /**
     * 加载插件,并获取插件自身的Context对象,以获取资源等信息 <p>
     * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < ClassLoader < Context(This) < Binder
     *
     * @param pluginName 插件名
     * @return 插件的Context对象
     * @since 1.0.0
     */
    public static Context fetchContext(String pluginName) {
        return PluginFactory.queryPluginContext(pluginName);
    }

    /**
     * 加载插件,并通过插件里的Plugin类,获取插件定义的IBinder <p>
     * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This) <p>
     * <p>
     * PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同: <p>
     * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p>
     * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册
     *
     * @param pluginName 插件名
     * @param module     要加载的插件模块
     * @param process    进程名 TODO 现阶段只能使用IPluginManager中的值,请务必使用它们,否则会出现问题
     * @return 返回插件定义的IBinder对象,供外界使用
     * @see #getGlobalBinder(String)
     * @since 1.0.0
     */
    public static IBinder fetchBinder(String pluginName, String module, String process) {
        return PluginFactory.query(pluginName, module, Integer.parseInt(process));
    }

    /**
     * 在当前进程加载插件,并通过插件里的Plugin类,获取插件定义的IBinder <p>
     * 注意:这里会尝试安装插件,并同时加载资源和代码,耗时可能较久 <p>
     * 性能消耗(小 → 大):ComponentList/PackageInfo < Resources < Context/ClassLoader < Binder(This) <p>
     * <p>
     * PluginBinder(如使用使用本方法)和GlobalBinder类方法(如getGlobalBinder)的不同: <p>
     * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p>
     * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册
     *
     * @param pluginName 插件名
     * @param module     要加载的插件模块
     * @return 返回插件定义的IBinder对象,供外界使用
     * @see #getGlobalBinder(String)
     * @since 2.1.0
     */
    public static IBinder fetchBinder(String pluginName, String module) {
        return PluginFactory.query(pluginName, module);
    }

    /**
     * 通过ClassLoader对象来获取该ClassLoader应属于哪个插件
     * <p>
     * 该方法消耗非常小,可直接使用
     *
     * @param cl ClassLoader对象
     * @return 插件名
     * @since 1.0.0
     */
    public static String fetchPluginNameByClassLoader(ClassLoader cl) {
        return PluginFactory.fetchPluginName(cl);
    }

    /**
     * 获取所有插件的列表(指已安装的)
     *
     * @return PluginInfo的表
     * @since 2.0.0(1.x版本为getExistPlugins)
     */
    public static List<PluginInfo> getPluginInfoList() {
        return RePluginOS.getPlugins(true);
    }

    /**
     * 获取指定插件的信息
     *
     * @param name 插件名
     * @return PluginInfo对象
     * @since 1.2.0
     */
    public static PluginInfo getPluginInfo(String name) {
        return RePluginOS.getPlugin(name, true);
    }

    /**
     * 获取当前插件的版本号,可以是VersionCode,也可以是meta-data中的ver。
     *
     * @param name 插件名
     * @return 插件版本号。若为-1则表示插件不存在
     * @since 2.0.0
     */
    public static int getPluginVersion(String name) {
        PluginInfo pi = RePluginOS.getPlugin(name, false);
        if (pi == null) {
            return -1;
        }

        return pi.getVersion();
    }

    /**
     * 判断插件是否已被安装(但不一定被使用过,如可能不会释放Dex、Native库等) <p>
     * 注意:RePlugin 1.x版本中,isPluginInstalled方法等于现在的isPluginUsed,故含义有变
     *
     * @param pluginName 插件名
     * @return 是否被安装
     * @since 2.0.0 (1.x版本为isPluginExists)
     */
    public static boolean isPluginInstalled(String pluginName) {
        PluginInfo pi = RePluginOS.getPlugin(pluginName, false);
        return pi != null;
    }

    /**
     * 判断插件是否曾被使用过。只要释放过Dex、Native的,就认为是“使用过”的 <p>
     * 和isPluginDexExtracted的区别:插件会在升级完成后,会删除旧Dex。其isPluginDexExtracted为false,而isPluginUsed仍为true
     *
     * @param pluginName 插件名
     * @return 插件是否已被使用过
     * @since 2.0.0
     */
    public static boolean isPluginUsed(String pluginName) {
        PluginInfo pi = RePluginOS.getPlugin(pluginName, false);
        return pi != null && pi.isUsed();
    }

    /**
     * 判断当前插件是否已释放了Dex、Native库等
     *
     * @param pluginName 插件名
     * @return 是否已被使用过
     * @since 2.0.0 (原为isPluginInstalled)
     */
    public static boolean isPluginDexExtracted(String pluginName) {
        PluginInfo pi = RePluginOS.getPlugin(pluginName, false);
        return pi != null && pi.isDexExtracted();
    }

    /**
     * 当前插件是否在运行。只要任意进程在,就都属于此情况
     *
     * @param pluginName 插件名
     * @return 插件是否正在被运行
     * @since 2.0.0
     */
    public static boolean isPluginRunning(String pluginName) {
        try {
            return PluginManagerProxy.isPluginRunning(pluginName);
        } catch (RemoteException e) {
            // 常驻进程中断,且当前进程也没有运行。先返回False
            if (LogRelease.LOGR) {
                e.printStackTrace();
            }
            return false;
        }
    }

    /**
     * 当前插件是否在指定进程中运行
     *
     * @param pluginName 插件名
     * @param process    指定的进程名,必须为全名
     * @return 插件是否在指定进程中运行
     * @since 2.0.0
     */
    public static boolean isPluginRunningInProcess(String pluginName, String process) {
        try {
            return PluginManagerProxy.isPluginRunningInProcess(pluginName, process);
        } catch (RemoteException e) {
            // 常驻进程中断,且当前进程也没有运行。先返回False
            if (LogRelease.LOGR) {
                e.printStackTrace();
            }
            return false;
        }
    }

    /**
     * 获取所有正在运行的插件列表
     *
     * @return 所有正在运行的插件的List
     * @see PluginRunningList
     * @since 2.0.0
     */
    public static PluginRunningList getRunningPlugins() {
        return PluginManagerProxy.getRunningPluginsNoThrows();
    }

    /**
     * 获取正在运行此插件的进程名列表 <p>
     * 若要获取PID,可在拿到列表后,通过IPC.getPidByProcessName来反查
     *
     * @param pluginName 要查询的插件名
     * @return 正在运行此插件的进程名列表。一定不会为Null
     * @see IPC#getPidByProcessName(String)
     * @since 2.0.0
     */
    public static String[] getRunningProcessesByPlugin(String pluginName) {
        return PluginManagerProxy.getRunningProcessesByPluginNoThrows(pluginName);
    }

    /**
     * 当前是否处于"常驻进程"?
     *
     * @return 是否处于常驻进程
     * @since 1.1.0
     */
    public static boolean isCurrentPersistentProcess() {
        return IPC.isPersistentProcess();
    }

    /**
     * 获取RePlugin的Config对象。请参见RePluginConfig类的说明
     *
     * @return RePluginConfig对象。注意,即便没有在attachBaseContext中自定义,也仍会有个默认的对象
     * @see RePluginConfig
     * @see App#attachBaseContext(Application, RePluginConfig)
     * @since 1.2.0
     */
    public static RePluginConfig getConfig() {
        return sConfig;
    }

    /**
     * 注册“安装完成后的通知”广播 <p>
     * 此为“本地”广播,插件内也可以接收到。开发者也可以自行注册,做法: <p>
     * <code>
     * IntentFilter itf = new IntentFilter(RePluginOS.ACTION_NEW_PLUGIN); <p>
     * LocalBroadcastManager.getInstance(context).registerReceiver(r, itf);
     * </code>
     *
     * @param context Context对象
     * @param r       要绑定的BroadcastReceiver对象
     * @since 1.0.0
     */
    public static void registerInstalledReceiver(Context context, BroadcastReceiver r) {
        IntentFilter itf = new IntentFilter(RePluginConstants.ACTION_NEW_PLUGIN);
        LocalBroadcastManager.getInstance(context).registerReceiver(r, itf);
    }

    /**
     * 在宿主内注册一个可被其它插件所获取的Binder的对象
     *
     * @param hbf 一个IHostBinderFetcher对象
     * @see #fetchBinder(String, String, String)
     * @since 1.0.0
     */
    public static void registerHostBinder(IHostBinderFetcher hbf) {
        RePluginOS.installBuiltinPlugin("main", hbf);
    }

    /**
     * 注册一个无需插件名,可被全局使用的Binder对象。Binder对象必须事先创建好 <p>
     * 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明 <p>
     * 有关此方法和registerGlobalBinderDelayed的区别,请参见其方法说明。
     *
     * @param name   Binder的描述名
     * @param binder Binder对象
     * @return 是否注册成功
     * @see #getGlobalBinder(String)
     * @see #registerGlobalBinderDelayed(String, IBinderGetter)
     * @since 1.2.0
     */
    public static boolean registerGlobalBinder(String name, IBinder binder) {
        return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, binder);
    }

    /**
     * 注册一个无需插件名,可被全局使用的Binder对象,但Binder对象只有在“用到时”才会被创建 <p>
     * 有关GlobalBinder的详细介绍,请参见getGlobalBinder的说明 <p>
     * <p>
     * 和registerGlobalBinder不同的是: <p>
     * 1、前者的binder对象必须事先创建好并传递到参数中 <p>
     *   适用于Binder在注册时就立即创建(性能消耗小),或未来使用频率非常多的情况。如“用户账号服务”、“基础服务”等 <p>
     * 2、后者会在getGlobalBinder指定的name被首次调用后,才会尝试获取Binder对象 <p>
     *   适用于Binder只在使用时才被创建(确保启动性能快),或未来调用频率较少的情况。如“Root服务”、“特色功能服务”等 <p>
     *
     * @param name   Binder的描述名
     * @param getter 当getGlobalBinder调用时匹配到name后,会调用getter.get()方法来获取IBinder对象
     * @return 是否延迟注册成功
     * @since 1.2.0
     */
    public static boolean registerGlobalBinderDelayed(String name, IBinderGetter getter) {
        return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, getter);
    }

    /**
     * 取消全局Binder对象的注册。这样当调用getGlobalBinder时将不再返回结果 <p>
     * 有关globalBinder的详细介绍,请参见registerGlobalBinder的说明
     *
     * @param name Binder的描述名
     * @return 是否取消成功
     * @see #getGlobalBinder(String)
     * @since 1.2.0
     */
    public static boolean unregisterGlobalBinder(String name) {
        return QihooServiceManager.removeService(RePluginInternal.getAppContext(), name, null);
    }

    /**
     * 获取一个无需插件名,可被全局使用的Binder对象。必须先调用registerGlobalBinder注册后才能获得 <p>
     * PluginBinder(如使用fetchBinder)和GlobalBinder类方法的不同: <p>
     * 1、PluginBinder需要指定插件;GlobalBinder无需指定 <p>
     * 2、PluginBinder获取的是插件内部已定义好的Binder;GlobalBinder在获取时必须先在代码中注册
     *
     * @param name Binder的描述名
     * @return Binder对象。若之前从未注册过,或已被取消注册,则返回Null。
     * @see #registerGlobalBinder(String, IBinder)
     * @see #unregisterGlobalBinder(String)
     * @see #fetchBinder(String, String, String)
     * @since 1.2.0
     */
    public static IBinder getGlobalBinder(String name) {
        return QihooServiceManager.getService(RePluginInternal.getAppContext(), name);
    }

    /**
     * 注册一个“跳转”类。一旦系统或自身想调用指定类时,将自动跳转到插件里的另一个类。 <p>
     * 例如,系统想访问CallShowService类,但此类在宿主中不存在,只在CallShow中有,则: <p>
     * 未注册“跳转类”时:直接到宿主中寻找CallShowService类,找到后就加载,找不到就崩溃(若不Catch) <p>
     * 注册“挑转类”后,直接将CallShowService的调用“跳转到”插件的CallShowService类中(名字可以不同)。这种情况下,需要调用: <p>
     * <code>
     * RePlugin.registerHookingClass("com.qihoo360.mobilesafe.CallShowService", <p>
     *             RePlugin.createComponentName("callshow", "com.qihoo360.callshow.CallShowService2"), <p>
     *             DummyService.class);
     * </code>
     * <p> <p>
     * 该方法可以玩出很多【新花样】。如,可用于以下场景: <p>
     * <b>* 已在宿主Manifest中声明了插件的四大组件,只是想借助插件化框架来找到该类并加载进来(如前述例子)。</b> <p>
     * <b>* 某些不方便(不好用,或需要云控)的类,想借机替换成插件里的。</b> <p>
     *   如我们有一个LaunchUtils类,现在想使用Utils插件中的同样的类来替代。
     *
     * @param source   要替换的类的全名
     * @param target   要替换的类的目标,需要使用 createComponentName 方法来创建
     * @param defClass 若要替换的类不存在,或插件不可用,则应该使用一个默认的Class。
     *                 <p>
     *                 可替换成如下的形式,也可以传Null。但若访问的是四大组件,传Null可能会导致出现App崩溃(且无法被Catch)
     *                 <p>
     *                 DummyService.class
     *                 <p>
     *                 DummyActivity.class
     *                 <p>
     *                 DummyProvider.class
     *                 <p>
     *                 DummyReceiver.class
     * @see com.qihoo360.replugin.component.dummy.DummyActivity
     * @see com.qihoo360.replugin.component.dummy.DummyService
     * @see com.qihoo360.replugin.component.dummy.DummyReceiver
     * @see com.qihoo360.replugin.component.dummy.DummyProvider
     * @since 1.0.0
     */
    public static void registerHookingClass(String source, ComponentName target, Class defClass) {
        Factory2.registerDynamicClass(source, target.getPackageName(), target.getClassName(), defClass);
    }

    /**
     * 查询某个 Component 是否是“跳转”类
     *
     * @param component 要查询的组件信息,其中 packageName 为插件名称,className 为要查询的类名称
     * @since 2.0.0
     */
    public static boolean isHookingClass(ComponentName component) {
        return Factory2.isDynamicClass(component.getPackageName(), component.getClassName());
    }

    /**
     * 取消对某个“跳转”类的注册,恢复原状。<p>
     * 请参见 registerHookingClass 的详细说明
     *
     * @param source   要替换的类的全名
     * @see #registerHookingClass(String, ComponentName, Class)
     * @since 2.1.6
     */
    public static void unregisterHookingClass(String source) {
        Factory2.unregisterDynamicClass(source);
    }

    /**
     * RePlugin中,针对Application的入口类 <p>
     * 所有针对Application的调用应从此类开始
     *
     * @author RePlugin Team
     */
    public static class App {

        static boolean sAttached;

        /**
         * 当Application的attachBaseContext调用时需调用此方法 <p>
         * 使用插件框架默认的方案
         *
         * @param app Application对象
         * @see Application#attachBaseContext(Context)
         */
        public static void attachBaseContext(Application app) {
            attachBaseContext(app, new RePluginConfig());
        }

        /**
         * 当Application的attachBaseContext调用时触发 <p>
         * 可自定义插件框架的回调行为。参见RePluginCallbacks类的说明
         * 此方法的定制性不如RePluginConfig的版本
         *
         * @param app Application对象
         * @param pc  可供外界使用的回调
         * @see Application#attachBaseContext(Context)
         * @see RePluginCallbacks
         */
        public static void attachBaseContext(Application app, RePluginCallbacks pc) {
            attachBaseContext(app, new RePluginConfig().setCallbacks(pc));
        }

        /**
         * (推荐)当Application的attachBaseContext调用时需调用此方法 <p>
         * 可自定义插件框架的行为。参见RePluginConfig类的说明
         *
         * @param app Application对象
         * @see Application#attachBaseContext(Context)
         * @see RePluginConfig
         * @since 1.2.0
         */
        public static void attachBaseContext(Application app, RePluginConfig config) {
            if (sAttached) {
                if (LogDebug.LOG) {
                    LogDebug.d(TAG, "attachBaseContext: Already called");
                }
                return;
            }

            RePluginInternal.init(app);
            sConfig = config;
            sConfig.initDefaults(app);

            IPC.init(app);

            // 打印当前内存占用情况
            // 只有开启“详细日志”才会输出,防止“消耗性能”
            if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
                LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, Start, pn=, framework, func=, attachBaseContext, lib=, RePlugin");
            }

            // 初始化HostConfigHelper(通过反射HostConfig来实现)
            // NOTE 一定要在IPC类初始化之后才使用
            HostConfigHelper.init();

            // FIXME 此处需要优化掉
            AppVar.sAppContext = app;

            // Plugin Status Controller
            PluginStatusController.setAppContext(app);

            PluginMgrFacade.init(app);
            PluginMgrFacade.callAttach();

            sAttached = true;
        }

        /**
         * 当Application的onCreate调用时触发。 <p>
         * 务必先调用attachBaseContext后,才能调用此方法
         *
         * @throws IllegalStateException 若没有调用attachBaseContext,则抛出此异常
         * @see Application#onCreate()
         */
        public static void onCreate() {
            if (!sAttached) {
                throw new IllegalStateException();
            }

            Tasks.init();

            PluginMgrFacade.callAppCreate();

            // 注册监听PluginInfo变化的广播以接受来自常驻进程的更新
            if (!IPC.isPersistentProcess()) {
                PluginInfoUpdater.register(RePluginInternal.getAppContext());
            }

            // 打印当前内存占用情况
            // 只有开启“详细日志”才会输出,防止“消耗性能”
            if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
                LogDebug.printMemoryStatus(LogDebug.TAG, "act=, init, flag=, End, pn=, framework, func=, onCreate, lib=, RePlugin");
            }
        }

        /**
         * 当Application的onLowMemory调用时触发 <p>
         * 除了插件化框架本身会做一些事情外,该方法也来通知插件onLowMemory的行为
         * <p>
         * 如果App的minSdkVersion >= 14,该方法不用调用
         *
         * @see Application#onLowMemory()
         */
        public static void onLowMemory() {
            // API>14采用注册回调的方式执行插件中该方法
            if (Build.VERSION.SDK_INT >= 14) {
                return;
            }

            // 遍历插件的Application对象,并调用其onLowMemory
            PluginApplicationClient.notifyOnLowMemory();
        }

        /**
         * 当Application的onTrimMemory调用时触发 <p>
         * 除了插件化框架本身会做一些事情外,该方法也来通知插件onTrimMemory的行为
         * <p>
         * 如果App的minSdkVersion >= 14,该方法不用调用
         *
         * @see Application#onTrimMemory(int)
         */
        public static void onTrimMemory(int level) {
            // API>14采用注册回调的方式执行插件中该方法
            if (Build.VERSION.SDK_INT >= 14) {
                return;
            }

            // 遍历插件的Application对象,并调用其onTrimMemory
            PluginApplicationClient.notifyOnTrimMemory(level);
        }

        /**
         * 当Application的onConfigurationChanged调用时触发 <p>
         * 除了插件化框架本身会做一些事情外,该方法也来通知插件onConfigurationChanged的行为
         * <p>
         * 如果App的minSdkVersion >= 14,该方法不用调用
         *
         * @see Application#onConfigurationChanged(Configuration)
         */
        public static void onConfigurationChanged(Configuration newConfig) {
            // API>14采用注册回调的方式执行插件中该方法
            if (Build.VERSION.SDK_INT >= 14) {
                return;
            }

            // 遍历插件的Application对象,并调用其onConfigurationChanged
            PluginApplicationClient.notifyOnConfigurationChanged(newConfig);
        }
    }
}