android插件化简要概述以及谈谈QQ的插件化原理大概

构建一个插件化了解几个关键点,就可以实现一个可以架构一个支持比较简单粗糙的插件化apk了,能拿到插件apk classloader的 就可以实现加载任意插件apk的类
知道Resources如何根据apk生成自然就可以操作任意布局,其他的就是使用类似包装模式一样的架构完成生命周期的代理.

动态加载插件apk dex

        File dexOutFileDir = context.getDir("dex", Context.MODE_PRIVATE);
String pluginApkPath=new File(context.getDir("plugin"),"my.apk").getAbsolutePath();
        dexClassLoader = new DexClassLoader(pluginApkPath, dexOutFileDir .getAbsolutePath() , null, context.getClassLoader());
//拿到了classloader就可以加载任意类了

动态加载资源

   try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, path);
            resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(context, "加载失败", Toast.LENGTH_SHORT).show();
        }

演示架构代理插件activity

//主activiy声明,但是不能是单例,这个不需要多说,其次在onCreate里面加载插件apk。
public class MyProxyActivity extends Activity {

    private String className;
    IPluginProxyActivity ipluginProxyActivity;
    // com.dongnao.alvin.taopiaopiao.MainActivity
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState  ) {
        super.onCreate(savedInstanceState );
        className = getIntent().getStringExtra("className");
//        class

        try {
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            Object instance= constructor.newInstance(new Object[]{});
//          可以
            ipluginProxyActivity = (IPluginProxyActivity) instance;

            ipluginProxyActivity.attach(this);
            Bundle bundle = new Bundle();
            ipluginProxyActivity.onCreate(bundle);

        } catch (Exception e) {

            Toast.makeText(this, "找不到class:"+className, Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className1=intent.getStringExtra("className");
        Intent intent1 = new Intent(this, MyProxyActivity.class);
        intent1.putExtra("className", className1);
        super.startActivity(intent1);
    }

    @Override
    public ComponentName startService(Intent service) {
        String serviceName = service.getStringExtra("serviceName");
        Intent intent1 = new Intent(this, ProxyService.class);
        intent1.putExtra("serviceName", serviceName);
        return super.startService(intent1);
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();//可以定义pluginId,方便扩展
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();//可以定义pluginId,方便扩展
    }


    @Override
    protected void onStart() {
        super.onStart();
        if(ipluginProxyActivity ==null){
            Toast.makeText(this, "获取payInterActivity fail", Toast.LENGTH_SHORT).show();
            return;
        }
        ipluginProxyActivity.onStart();
    }

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

    @Override
    protected void onPause() {
        super.onPause();
        ipluginProxyActivity.onPause();
    }
}

根据资源id查资源名或反查



    public int getIdentifier(int resId) {
        if (isDefaultSkin) {
            return resId;
        }
        //在皮肤包中不一定就是 当前程序的 id
        //获取对应id 在当前的名称 colorPrimary
        //R.drawable.ic_launcher
        String resName = mAppResources.getResourceEntryName(resId);//ic_launcher
        String resType = mAppResources.getResourceTypeName(resId);//drawable
        int skinId = mSkinResources.getIdentifier(resName, resType, mSkinPkgName);
        return skinId;
    }

    public int getColor(int resId) {
        if (isDefaultSkin) {
            return mAppResources.getColor(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getColor(resId);
        }
        return mSkinResources.getColor(skinId);
    }

    public ColorStateList getColorStateList(int resId) {
        if (isDefaultSkin) {
            return mAppResources.getColorStateList(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getColorStateList(resId);
        }
        return mSkinResources.getColorStateList(skinId);
    }

    public Drawable getDrawable(int resId) {
        //如果有皮肤  isDefaultSkin false 没有就是true
        if (isDefaultSkin) {
            return mAppResources.getDrawable(resId);
        }
        int skinId = getIdentifier(resId);
        if (skinId == 0) {
            return mAppResources.getDrawable(resId);
        }
        return mSkinResources.getDrawable(skinId);
    }

hook拦截activity启动

//可以追踪startAcitivty最终调用哪里
Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
 Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
 gDefaultField.setAccessible(true);
 Object gDefault = gDefaultField.get(null);
 // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
 Class singleton = Class.forName("android.util.Singleton");
 Field mInstanceField = singleton.getDeclaredField("mInstance");
 mInstanceField.setAccessible(true);
 // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
 Object rawIActivityManager = mInstanceField.get(gDefault);
 // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
 Class iActivityManagerInterface = Class.forName("android.app.IActivityManager")
 Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader
 new Class[] { iActivityManagerInterface }, new IActivityManagerHandler(
 mInstanceField.set(gDefault, proxy);

反射实现静态recervicer变成动态加载

//反射的代码 根据 如下2个文件源码分析总结
  
//\frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
//\frameworks\base\core\java\android\content\pm\PackageParser.java
        try {

            Class   packageParserClass = Class.forName("android.content.pm.PackageParser");
            Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
            Object packageParser = packageParserClass.newInstance();
           Object packageObj=  parsePackageMethod.invoke(packageParser, new File(path), PackageManager.GET_ACTIVITIES);
            Field receiverField=packageObj.getClass().getDeclaredField("receivers");
            List receivers = (List) receiverField.get(packageObj);

            Class componentClass = Class.forName("android.content.pm.PackageParser$Component");
            Field intentsField = componentClass.getDeclaredField("intents");

            // 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成
            Class packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");
//            generateActivityInfo方法
            Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
           Object defaltUserState= packageUserStateClass.newInstance();
            Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo",
                    packageParser$ActivityClass, int.class, packageUserStateClass, int.class);
            Class userHandler = Class.forName("android.os.UserHandle");
            Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
            int userId = (int) getCallingUserIdMethod.invoke(null);
            for (Object activity : receivers) {
               ActivityInfo info= (ActivityInfo) generateReceiverInfo.invoke(packageParser,  activity,0, defaltUserState, userId);
               BroadcastReceiver broadcastReceiver= (BroadcastReceiver) dexClassLoader.loadClass(info.name).newInstance();
                List intents= (List) intentsField.get(activity);
                for (IntentFilter intentFilter : intents) {
                    context.registerReceiver(broadcastReceiver, intentFilter);
                }
            }
            //generateActivityInfo
        } catch (Exception e) {
            e.printStackTrace();
        }

定义接口实现类加载后直接强转接口

支持拦截的代理接口

//Proxy 只支持接口,因此没有定义接口的没戏
public interface IActivityManager extends IInterface {

    public int startActivity(IApplicationThread caller,
            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
            boolean onlyIfNeeded, boolean debug) throws RemoteException;
    public WaitResult startActivityAndWait(IApplicationThread caller,
            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
            boolean onlyIfNeeded, boolean debug) throws RemoteException;
    public int startActivityWithConfig(IApplicationThread caller,
            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
            boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException;
    public int startActivityIntentSender(IApplicationThread caller,
            IntentSender intent, Intent fillInIntent, String resolvedType,
            IBinder resultTo, String resultWho, int requestCode,
            int flagsMask, int flagsValues) throws RemoteException;
    public boolean startNextMatchingActivity(IBinder callingActivity,
            Intent intent) throws RemoteException;
    public boolean finishActivity(IBinder token, int code, Intent data)
            throws RemoteException;
    public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
    public boolean willActivityBeVisible(IBinder token) throws RemoteException;
    public Intent registerReceiver(IApplicationThread caller,
            IIntentReceiver receiver, IntentFilter filter,
            String requiredPermission) throws RemoteException;
    public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException;
    public static final int BROADCAST_SUCCESS = 0;
    public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
    public int broadcastIntent(IApplicationThread caller, Intent intent,
            String resolvedType, IIntentReceiver resultTo, int resultCode,
            String resultData, Bundle map, String requiredPermission,
            boolean serialized, boolean sticky) throws RemoteException;
    public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException;
    /* oneway */
    public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException;
    public void attachApplication(IApplicationThread app) throws RemoteException;
    /* oneway */
    public void activityIdle(IBinder token, Configuration config) throws RemoteException;
    public void activityPaused(IBinder token, Bundle state) throws RemoteException;
    /* oneway */
    public void activityStopped(IBinder token,
                                Bitmap thumbnail, CharSequence description) throws RemoteException;
    /* oneway */
    public void activityDestroyed(IBinder token) throws RemoteException;
    public String getCallingPackage(IBinder token) throws RemoteException;
    public ComponentName getCallingActivity(IBinder token) throws RemoteException;
    public List getTasks(int maxNum, int flags,
                         IThumbnailReceiver receiver) throws RemoteException;
    public List getRecentTasks(int maxNum,
            int flags) throws RemoteException;
    public List getServices(int maxNum, int flags) throws RemoteException;
    public List getProcessesInErrorState()
            throws RemoteException;
    public void moveTaskToFront(int task) throws RemoteException;
    public void moveTaskToBack(int task) throws RemoteException;
    public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
    public void moveTaskBackwards(int task) throws RemoteException;
    public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
    public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException;
    /* oneway */
    public void reportThumbnail(IBinder token,
            Bitmap thumbnail, CharSequence description) throws RemoteException;
    public ContentProviderHolder getContentProvider(IApplicationThread caller,
            String name) throws RemoteException;
    public void removeContentProvider(IApplicationThread caller,
            String name) throws RemoteException;
    public void publishContentProviders(IApplicationThread caller,
            List providers) throws RemoteException;
    public PendingIntent getRunningServiceControlPanel(ComponentName service)
            throws RemoteException;
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType) throws RemoteException;
    public int stopService(IApplicationThread caller, Intent service,
            String resolvedType) throws RemoteException;
    public boolean stopServiceToken(ComponentName className, IBinder token,
            int startId) throws RemoteException;
    public void setServiceForeground(ComponentName className, IBinder token,
            int id, Notification notification, boolean keepNotification) throws RemoteException;
    public int bindService(IApplicationThread caller, IBinder token,
            Intent service, String resolvedType,
            IServiceConnection connection, int flags) throws RemoteException;
    public boolean unbindService(IServiceConnection connection) throws RemoteException;
    public void publishService(IBinder token,
            Intent intent, IBinder service) throws RemoteException;
    public void unbindFinished(IBinder token, Intent service,
            boolean doRebind) throws RemoteException;
    /* oneway */
    public void serviceDoneExecuting(IBinder token, int type, int startId,
            int res) throws RemoteException;
    public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
    
    public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
            throws RemoteException;
    public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
    public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;
    public void killApplicationProcess(String processName, int uid) throws RemoteException;
    
    public boolean startInstrumentation(ComponentName className, String profileFile,
            int flags, Bundle arguments, IInstrumentationWatcher watcher)
            throws RemoteException;
    public void finishInstrumentation(IApplicationThread target,
            int resultCode, Bundle results) throws RemoteException;

    public Configuration getConfiguration() throws RemoteException;
    public void updateConfiguration(Configuration values) throws RemoteException;
    public void setRequestedOrientation(IBinder token,
            int requestedOrientation) throws RemoteException;
    public int getRequestedOrientation(IBinder token) throws RemoteException;
    
    public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;
    public String getPackageForToken(IBinder token) throws RemoteException;

    public static final int INTENT_SENDER_BROADCAST = 1;
    public static final int INTENT_SENDER_ACTIVITY = 2;
    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
    public static final int INTENT_SENDER_SERVICE = 4;
    public IIntentSender getIntentSender(int type,
            String packageName, IBinder token, String resultWho,
            int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException;
    public void cancelIntentSender(IIntentSender sender) throws RemoteException;
    public boolean clearApplicationUserData(final String packageName,
            final IPackageDataObserver observer) throws RemoteException;
    public String getPackageForIntentSender(IIntentSender sender) throws RemoteException;
    
    public void setProcessLimit(int max) throws RemoteException;
    public int getProcessLimit() throws RemoteException;
    
    public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException;
    
    public int checkPermission(String permission, int pid, int uid)
            throws RemoteException;

    public int checkUriPermission(Uri uri, int pid, int uid, int mode)
            throws RemoteException;
    public void grantUriPermission(IApplicationThread caller, String targetPkg,
            Uri uri, int mode) throws RemoteException;
    public void revokeUriPermission(IApplicationThread caller, Uri uri,
            int mode) throws RemoteException;
    
    public void showWaitingForDebugger(IApplicationThread who, boolean waiting)
            throws RemoteException;
    
    public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
    
    public void killBackgroundProcesses(final String packageName) throws RemoteException;
    public void forceStopPackage(final String packageName) throws RemoteException;
    
    // Note: probably don't want to allow applications access to these.
    public void goingToSleep() throws RemoteException;
    public void wakingUp() throws RemoteException;
    
    public void unhandledBack() throws RemoteException;
    public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
    public void setDebugApp(
        String packageName, boolean waitForDebugger, boolean persistent)
        throws RemoteException;
    public void setAlwaysFinish(boolean enabled) throws RemoteException;
    public void setActivityController(IActivityController watcher)
        throws RemoteException;

    public void enterSafeMode() throws RemoteException;
    
    public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
    
    public boolean killPids(int[] pids, String reason) throws RemoteException;
//省略了很多............................

我利用这个技术做了什么?

我的机器人插件功能就是这样实现的,各位想开发QQ机器人插件的朋友欢迎加入哦!
插件sdk支持禁言,踢人

插件demo地址
https://gitee.com/51bwn/RobotPluginSDK
插件机器人没有ui界面哈

  DexClassLoader dexClassLoader = new DexClassLoader(currentApk.getAbsolutePath(), getDefaultPluginDexPath(context).getAbsolutePath(), null, context.getClassLoader());
                    try {
                        Class aClass = dexClassLoader.loadClass(Cns.PLUGIN_MAIN_ENTRY_FILE);
                        PluginInterface pluginInterface = (PluginInterface) aClass.newInstance();
                        pluginInterface.onCreate(context);
                        QueryPluginModel model = new QueryPluginModel();
                        model.setPluginInterface(pluginInterface);
                        model.setOfficial(pluginInterface.getPackageName().startsWith(BuildConfig.APPLICATION_ID));
                        model.setPath(currentApk.getAbsolutePath());
                        if (onCreateNotify != null) {
                            onCreateNotify.onEach(pluginInterface);
                        }
                        list.add(model);
                        if (BuildConfig.DEBUG) {

                            Log.w(TAG, "加载插件" + currentApk.getAbsolutePath() + "成功");
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        Log.e(TAG, "加载" + currentApk.getName() + "文件插件失败,非机器人插件或插件apk被混淆,导致找不到" + Cns.PLUGIN_MAIN_ENTRY_FILE + "类");
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        Log.e(TAG, "加载插件失败,无法创建对象 IllegalAccessException 可能没有访问权限");
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                        Log.e(TAG, "加载插件失败,无法创建对象InstantiationException ");
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.e(TAG, "加载插件失败,未知异常 " + e.getMessage());
                    } catch (Error e) {
                        e.printStackTrace();
                        Log.e(TAG, "加载插件失败,未知错误 " + e.getMessage());
                    }

上面的方式是固定的class规定,实际上也可以读取资产目录,然后只读取资产目录文件里面规定的类型,

//PluginInterface 文件
/**
 * Created by qssq on 2018/1/21 [email protected]
 */

public interface PluginInterface {
    /**
     * 插件作者
     * @return
     */
    public String getAuthorName();
    /**
     * 当机器人插件被创建加载后,会回调配置api控制类给插件,插件可以自己存储为成员变量,在适当的逻辑中进行操作,此api接口回调支持踢人禁言,发消息
     * @param instance
     */
    public void onReceiveControlApi(PluginCtronolInterface instance);

    /**
     * 插件的版本号
     * 
     * @return
     */
    public int getVersionCode();
    public String getBuildTime();
    /**
     * 
     * @return 插件的版本名
     */
    public String getVersionName();

    /**
     * 
     * @return 插件的包名(不要和作者的包名一样!否则)为了用户的安全,恶意流氓插件我会强制处理,对于冒充官方包名的插件校验签名。所以不要用cn.
     *         qssq666开头的包名。
     */
    public String getPackageName();

    /**
     * 此机器人插件的描述信息
     * 
     * @return 返回此插件的描述信息
     */
    public String getDescript();
    /**
     * 插件的名字,这里当然是中文名了,给用户看的名字嘛
     * @return
     */
    public String getPluginName();

    // boolean isOfficial();
    /**
     * 本插件是否被禁用了,这和卸载不同,卸载的话文件都删除了,但是是否禁用是由用户自己实现的
     * 
     * @return
     */
    boolean isDisable();

    /**
     * ,在修改机器人插件配置右边的选项卡会调用setDisable
     * 不写无法禁用,影响体验,导致的后果就是被用户卸载咯!具体怎么实现禁用请参考demo源码
     * 
     * @param disable
     */
    void setDisable(boolean disable);

    /**
     * 插件被加载进来后会把宿主的上下文传递过来,不过我感觉有风险啊,有了这东西什么都可以反射了。
     * 
     * @param context
     */
    void onCreate(Context context);

    /**
     * 这里是处理逻辑的关键,首先自身命令和管理员命令逻辑会先走,之后走这里提供消息判断是否处理了此消息,如果处理了就返回true,
     * 
     * @param item
     * @return 插件处理了本消息就返回true,那么其他插件,或者机器人将不会被处理,防止冲突。
     */
    boolean onReceiveMsgIsNeedIntercept(MsgItem item);

    /**
     * 这个方法暂时没用,
     */
    void onDestory();

    /**
     * 插件刚加载后会提供一个机器人配置查询接口,比如已经禁用了群聊功能了,你这个机器人插件不听话还进行处理,那么下场也是被卸载了.
     * 
     * @param robotConfigInterface
     */
    void onReceiveRobotConfig(RobotConfigInterface robotConfigInterface);

}

分析手机QQ插件化原理

//com.tencent.mobileqq.pluginsdk.PluginProxyActivity
    private void b() throws Exception {
        PackageInfo packageInfoWithException;
        PackageInfo packageInfo = (PackageInfo) PluginStatic.d.get(this.g);
        if (packageInfo == null) {
            if (DebugHelper.sDebug) {
                DebugHelper.log("PluginProxyActivity.initPlugin start getPackageInfo");
            }
            try {
                packageInfoWithException = ApkFileParser.getPackageInfoWithException(this, this.g, 129);
                if (DebugHelper.sDebug) {
                    DebugHelper.log("PluginProxyActivity.initPlugin end getPackageInfo, " + packageInfoWithException);
                }
                if (packageInfoWithException == null) {
                    throw new a("Get Package Info Failed!");
                }
                PluginStatic.d.put(this.g, packageInfoWithException);
            } catch (Throwable th) {
                a aVar = new a("getPackageInfoWithException", th);
            }
        } else {
            packageInfoWithException = packageInfo;
        }
        if (this.j == null || this.j.length() == 0) {
            if (packageInfoWithException.activities == null || packageInfoWithException.activities.length == 0) {
                throw new b();
            }
            this.j = packageInfoWithException.activities[0].name;
        }
        ClassLoader a = PluginStatic.a((Context) this, this.i, this.g);
        getIntent().setExtrasClassLoader(a);
        if (DebugHelper.sDebug) {
            DebugHelper.log("PluginProxyActivity.initPlugin start loadClass");
        }
        this.d = a.loadClass(this.j);
        if (DebugHelper.sDebug) {
            DebugHelper.log("PluginProxyActivity.initPlugin start loadClass");
        }
        this.mPluginActivity = (IPluginActivity) this.d.newInstance();
        this.mPluginActivity.IInit(this.i, this.g, this, a, packageInfoWithException, this.e, this.f);
        Intent intent = new Intent(getIntent());
        Bundle bundleExtra = intent.getBundleExtra(INNER_INTENT_EXTRAS);
        if (bundleExtra != null) {
            intent.putExtras(bundleExtra);
            intent.removeExtra(INNER_INTENT_EXTRAS);
        }
        this.mPluginActivity.ISetIntent(intent);
    }

分析com.tencent.mobileqq.pluginsdk.PluginStatic

public abstract class PluginStatic {
    public static final String PARAM_CLASS_STATISTICS_UPLOADER = "clsUploader";
    public static final String PARAM_CLEAR_TOP = "cleartop";
    public static final String PARAM_EXTRA_INFO = "pluginsdk_extraInfo";
    public static final String PARAM_LAUNCH_ACTIVITY = "pluginsdk_launchActivity";
    public static final String PARAM_LAUNCH_SERVICE = "pluginsdk_launchService";
    public static final String PARAM_PATH = "pluginsdk_pluginpath";
    public static final String PARAM_PLUGIN_GESTURELOCK = "param_plugin_gesturelock";
    public static final String PARAM_PLUGIN_INTERNAL_ACTIVITIES_ONLY = "PARAM_PLUGIN_INTERNAL_ACTIVITIES_ONLY";
    public static final String PARAM_PLUGIN_LOCATION = "pluginsdk_pluginLocation";
    public static final String PARAM_PLUGIN_NAME = "pluginsdk_pluginName";
    public static final String PARAM_PLUGIN_RECEIVER_CLASS_NAME = "pluginsdk_launchReceiver";
    public static final String PARAM_UIN = "pluginsdk_selfuin";
    public static final String PARAM_USE_QQ_RESOURCES = "userQqResources";
    public static final String PARAM_USE_SKIN_ENGINE = "useSkinEngine";
    public static final int USER_QQ_RESOURCES_BOTH = 2;
    public static final int USER_QQ_RESOURCES_NO = -1;
    public static final int USER_QQ_RESOURCES_YES = 1;
    static final String a = "com.tencent.mobileqq";
    static final String b = "pluginsdk_IsPluginActivity";
    static final ConcurrentHashMap c = new ConcurrentHashMap();
    static final ConcurrentHashMap d = new ConcurrentHashMap();
    private static final HashMap e = new HashMap();
    private static ArrayList f = new ArrayList();

    public interface IPluginLife {
        void onLoad();

        void onUnload();
    }
//上下文 读取    插件的id,插件apk的路径
    static synchronized ClassLoader a(Context context, String str, String str2) throws Exception {
        ClassLoader classLoader;
        synchronized (PluginStatic.class) {
            classLoader = (DexClassLoader) c.get(str);
            if (classLoader == null) {
                ClassLoader soDexClassLoader;
                QLog.d("plugin_tag", 1, "getOrCreateClassLoader:" + str);
                long currentTimeMillis = System.currentTimeMillis();
                String canonicalPath = PluginUtils.getOptimizedDexPath(context).getCanonicalPath();
                String canonicalPath2 = PluginUtils.getPluginLibPath(context, str).getCanonicalPath();
                if (str2.endsWith(".so")) {
                    soDexClassLoader = new SoDexClassLoader(str2, canonicalPath, canonicalPath2, context.getClassLoader());
                } else {
                    if (str.startsWith("qzone_live_video_plugin")) {
                        long currentTimeMillis2 = System.currentTimeMillis();
                        ClassLoader orCreateClassLoader = getOrCreateClassLoader(context, "qzone_plugin.apk");
                        QLog.d("plugin_tag", 1, "get qzone classloader cost=" + (System.currentTimeMillis() - currentTimeMillis2));
                        soDexClassLoader = new QZoneLiveClassLoader(str2, canonicalPath, canonicalPath2, orCreateClassLoader);
                    } else {
                        String str3;
                        if (str2 != null) {
                            if (!new File(str2).exists()) {
                                QLog.d("plugin_tag", 1, "getOrCreateClassLoader notExist  " + str2);
                                classLoader = new DexClassLoader("", canonicalPath, canonicalPath2, context.getClassLoader());
                            }
                        }
                        if (PluginUtils.isOsNeedReleaseDex() && IPluginAdapterProxy.getProxy().isSupportMultiDex(str)) {
                            File multiDexSecondDex = PluginUtils.getMultiDexSecondDex(context, str);
                            if (multiDexSecondDex.exists()) {
                                str3 = str2 + File.pathSeparator + multiDexSecondDex.getAbsolutePath();
                                QLog.d("plugin_tag", 1, "multiDex dexsPath" + str3);
                                QLog.d("plugin_tag", 1, "dexsPath" + str3);
                                soDexClassLoader = new DexClassLoader(str3, canonicalPath, canonicalPath2, context.getClassLoader());
                            }
                        }
                        str3 = str2;
                        QLog.d("plugin_tag", 1, "dexsPath" + str3);
                        soDexClassLoader = new DexClassLoader(str3, canonicalPath, canonicalPath2, context.getClassLoader());
                    }
                }
                PackageInfo packageInfo = (PackageInfo) d.get(str2);
                if (packageInfo == null) {
                    try {
                        packageInfo = ApkFileParser.getPackageInfoWithException(context, str2, 129);
                    } catch (Throwable th) {
                        DebugHelper.log("plugin_tag", "PluginStatic.getOrCreateClassLoaderByPath Get Package Info Failed!", th);
                    }
                    if (packageInfo == null) {
                        DebugHelper.log("PluginStatic.getOrCreateClassLoaderByPath Get Package Info Failed! " + new File(str2).exists());
                    }
                    d.put(str2, packageInfo);
                }
                if (packageInfo != null) {
                    a(packageInfo, str, soDexClassLoader);
                }
                QLog.w("plugin_tag", 1, "getOrCreateClassLoaderCost:" + str + " c:" + (System.currentTimeMillis() - currentTimeMillis));
                c.put(str, soDexClassLoader);
                classLoader = soDexClassLoader;
            }
        }
        return classLoader;
    }

    static List a() {
        return f;
    }

    static void a(PackageInfo packageInfo, String str, ClassLoader classLoader) {
        try {
            if (((IPluginLife) e.get(str)) == null && packageInfo != null && packageInfo.applicationInfo != null && packageInfo.applicationInfo.metaData != null) {
                String string = packageInfo.applicationInfo.metaData.getString("PLUGIN_LIFE_CLASS");
                if (string != null) {
                    IPluginLife iPluginLife = (IPluginLife) classLoader.loadClass(string).newInstance();
                    e.put(str, iPluginLife);
                    iPluginLife.onLoad();
                }
            }
        } catch (Throwable th) {
        }
    }

    static void a(IPluginActivity iPluginActivity) {
        b();
        synchronized (f) {
            f.add(new WeakReference(iPluginActivity));
        }
    }

    static boolean a(Bundle bundle) {
        if (bundle == null) {
            return false;
        }
        try {
            String string = bundle.getString(PARAM_PLUGIN_LOCATION);
            if (TextUtils.isEmpty(string) || string.substring(0, string.lastIndexOf(".")).contains(".") || TextUtils.isEmpty(bundle.getString(PARAM_PLUGIN_NAME))) {
                return false;
            }
            string = bundle.getString(PARAM_PATH);
            return !TextUtils.isEmpty(string) ? TextUtils.isEmpty(string) ? true : a(string) : false;
        } catch (Throwable th) {
            return false;
        }
    }

    static boolean a(String str) {
        try {
            if (str.contains("..")) {
                return false;
            }
            if (!str.endsWith(".so")) {
                return str.endsWith(".apk") ? a(str, PluginUtils.getPluginInstallDir(BaseApplication.getContext())) : false;
            } else {
                String parent = BaseApplication.getContext().getFilesDir().getParent();
                File file = new File(parent + SoLoadCore.PATH_TX_LIB);
                if (a(str, new File(parent + SoLoadCore.PATH_LIB)) || a(str, file)) {
                }
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean a(String str, File file) throws IOException {
        String canonicalPath = file.getCanonicalPath();
        String canonicalPath2 = new File(str).getParentFile().getCanonicalPath();
        if (QLog.isColorLevel()) {
            QLog.d("plugin_tag", 2, "path:" + str + "-> [" + canonicalPath2 + ", " + canonicalPath + "]");
        }
        return canonicalPath2.equals(canonicalPath);
    }

    static void b() {
        synchronized (f) {
            int i = 0;
            while (i < f.size()) {
                int i2;
                if (((WeakReference) f.get(i)).get() == null) {
                    f.remove(i);
                    i2 = i - 1;
                } else {
                    i2 = i;
                }
                i = i2 + 1;
            }
        }
    }

    static void b(IPluginActivity iPluginActivity) {
        b();
        c(iPluginActivity);
    }

    public static String byteArrayToHex(byte[] byteArray) {
        int i = 0;
        char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] cArr2 = new char[(byteArray.length * 2)];
        int length = byteArray.length;
        int i2 = 0;
        while (i < length) {
            byte b = byteArray[i];
            int i3 = i2 + 1;
            cArr2[i2] = cArr[(b >>> 4) & 15];
            i2 = i3 + 1;
            cArr2[i3] = cArr[b & 15];
            i++;
        }
        return new String(cArr2);
    }

    private static boolean c(IPluginActivity iPluginActivity) {
        synchronized (f) {
            for (int i = 0; i < f.size(); i++) {
                if (((WeakReference) f.get(i)).get() == iPluginActivity) {
                    f.remove(i);
                    return true;
                }
            }
            return false;
        }
    }

    public static String encodeFile(String filePath) {
        Throwable e;
        String str = "";
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            FileInputStream fileInputStream;
            try {
                byte[] bArr;
                MessageDigest instance = MessageDigest.getInstance("MD5");
                fileInputStream = new FileInputStream(file);
                try {
                    bArr = new byte[16384];
                } catch (OutOfMemoryError e2) {
                    bArr = new byte[4096];
                }
                while (true) {
                    try {
                        int read = fileInputStream.read(bArr);
                        if (read == -1) {
                            break;
                        }
                        instance.update(bArr, 0, read);
                    } catch (Exception e3) {
                        e = e3;
                    }
                }
                String byteArrayToHex = byteArrayToHex(instance.digest());
                if (fileInputStream == null) {
                    return byteArrayToHex;
                }
                try {
                    fileInputStream.close();
                    return byteArrayToHex;
                } catch (IOException e4) {
                    return byteArrayToHex;
                }
            } catch (Exception e5) {
                e = e5;
                fileInputStream = null;
                try {
                    if (QLog.isColorLevel()) {
                        QLog.e("plugin_tag", 2, "encode-Exception:" + QLog.getStackTraceString(e));
                    }
                    if (fileInputStream != null) {
                        try {
                            fileInputStream.close();
                        } catch (IOException e6) {
                        }
                    }
                    return str;
                } catch (Throwable th) {
                    e = th;
                    if (fileInputStream != null) {
                        try {
                            fileInputStream.close();
                        } catch (IOException e7) {
                        }
                    }
                    throw e;
                }
            } catch (Throwable th2) {
                e = th2;
                fileInputStream = null;
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                throw e;
            }
        }
        if (QLog.isColorLevel()) {
            QLog.e("plugin_tag", 2, "encode-File does not exist or is not file");
        }
        return str;
    }

    public static synchronized ClassLoader getClassLoader(String pluginID) {
        DexClassLoader dexClassLoader;
        synchronized (PluginStatic.class) {
            dexClassLoader = (DexClassLoader) c.get(pluginID);
        }
        return dexClassLoader;
    }
//加载红包插件也是这样玩的,实际上就是加载一个apk的dexclassloader,如果已缓存就从内从中取出,否则就加载一个, 传递plugindId,和 安装路径就饿可到了一个新的classloader
    public static synchronized ClassLoader getOrCreateClassLoader(Context c, String pluginID) throws Exception {
        ClassLoader classLoader;
        synchronized (PluginStatic.class) {
            classLoader = (ClassLoader) c.get(pluginID);
            if (classLoader == null) {
                classLoader = a(c, pluginID, PluginUtils.getInstalledPluginPath(c, pluginID).getCanonicalPath());
            }
        }
        return classLoader;
    }

    public static List isProcessesExist(Context c, List processNames) {
        if (processNames == null) {
            return null;
        }
        List arrayList = new ArrayList();
        List runningAppProcesses = ((ActivityManager) c.getSystemService("activity")).getRunningAppProcesses();
        if (runningAppProcesses == null) {
            for (int i = 0; i < processNames.size(); i++) {
                arrayList.add(Boolean.FALSE);
            }
            return arrayList;
        }
        for (String str : processNames) {
            boolean z;
            for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
                if (str.equalsIgnoreCase(runningAppProcessInfo.processName)) {
                    z = true;
                    break;
                }
            }
            z = false;
            arrayList.add(Boolean.valueOf(z));
        }
        return arrayList;
    }

    public static synchronized ClassLoader removeClassLoader(String pluginId) {
        ClassLoader classLoader;
        synchronized (PluginStatic.class) {
            QLog.d("plugin_tag", 1, "removeClassLoader:" + pluginId);
            classLoader = (ClassLoader) c.remove(pluginId);
        }
        return classLoader;
    }
}

分析QQ如何加载插件apk的so文件


public class PluginUtils {
    public static final String CONFIG_FILE_EXTEND_NAME = ".cfg";
    private static final int a = 8192;
    private static Map b = new ConcurrentHashMap();
    private static Map c = new ConcurrentHashMap();
    private static final String d = ".tmp";

    static class a extends Exception {
        private static final long a = 1;

        public a(String str) {
            super(str);
        }
    }

    static class b extends Exception {
        private static final long a = 1;

        b() {
        }
    }


    public static String getExceptionInfo(Throwable t) {
        while (t.getCause() != null) {
            t = t.getCause();
        }
        Writer stringWriter = new StringWriter();
        t.printStackTrace(new PrintWriter(stringWriter, true));
        return stringWriter.getBuffer().toString();
    }
}

从上面的源码可以看出来extractLibs方法做了这个事情,首先把apk文件读入到QZipFile,然后 得到实体遍历文件夹得到真正的apk路径。

上面的代码目前还无法得知干嘛的,暂时放一放谈谈pluginId,qq定义为一个没有路径的apk文件名

 public String getPluginID() {
        return "qwallet_plugin.apk";
    }

QWalletPluginProxyActivity红包界面源码可以得知是这样写的。

腾讯QQ的插件activity接口

public interface IPluginActivity {
    boolean IDispatchTouchEvent(MotionEvent motionEvent);

    void IFinish();

    View IGetContentView();

    Handler IGetInHandler();

    Resources IGetResource();

    void IInit(String str, String str2, Activity activity, ClassLoader classLoader, PackageInfo packageInfo, boolean z, int i);

    boolean IIsWrapContent();

    void IOnActivityResult(int i, int i2, Intent intent);

    void IOnAttachFragment(Fragment fragment);

    boolean IOnBackPressed();

    void IOnConfigurationChanged(Configuration configuration);

    void IOnCreate(Bundle bundle);

    boolean IOnCreateOptionsMenu(Menu menu);

    void IOnDestroy();

    boolean IOnKeyDown(int i, KeyEvent keyEvent);

    boolean IOnKeyMultiple(int i, int i2, KeyEvent keyEvent);

    boolean IOnKeyUp(int i, KeyEvent keyEvent);

    boolean IOnMenuItemSelected(int i, MenuItem menuItem);

    void IOnNewIntent(Intent intent);

    boolean IOnOptionsItemSelected(MenuItem menuItem);

    void IOnPause();

    boolean IOnPrepareOptionsMenu(Menu menu);

    void IOnRestart();

    void IOnRestoreInstanceState(Bundle bundle);

    void IOnResume();

    void IOnSaveInstanceState(Bundle bundle);

    void IOnSetTheme();

    void IOnStart();

    void IOnStop();

    boolean IOnTouchEvent(MotionEvent motionEvent);

    void IOnUserInteraction();

    void IOnWindowFocusChanged(boolean z);

    void ISetIntent(Intent intent);

    void ISetIsTab();

    void ISetOutHandler(Handler handler);

    void ISetParent(BasePluginActivity basePluginActivity);

    ImmersiveConfig IgetImmersiveConfig();
}


实现类BasePluginActivity

android插件化简要概述以及谈谈QQ的插件化原理大概_第1张图片
image.png

this.f就是插件apk,说明如果是插件模式的话就调用的是this.f ,也就是说这个插件apk是可以单独运行的。

还是不看了,感觉由于不能跟踪代码,看起来头晕
项目庞大,一般的分析工具都卡死了。

再谈谈android 的其他hook 如破解签名hook



interface IPackageManager {
    PackageInfo getPackageInfo(String packageName, int flags);
    int getPackageUid(String packageName);
    int[] getPackageGids(String packageName);
    
    String[] currentToCanonicalPackageNames(in String[] names);
    String[] canonicalToCurrentPackageNames(in String[] names);

    PermissionInfo getPermissionInfo(String name, int flags);
    
    List queryPermissionsByGroup(String group, int flags);
    
    PermissionGroupInfo getPermissionGroupInfo(String name, int flags);
    
    List getAllPermissionGroups(int flags);
    
    ApplicationInfo getApplicationInfo(String packageName, int flags);

    ActivityInfo getActivityInfo(in ComponentName className, int flags);

    ActivityInfo getReceiverInfo(in ComponentName className, int flags);

    ServiceInfo getServiceInfo(in ComponentName className, int flags);

    ProviderInfo getProviderInfo(in ComponentName className, int flags);

    int checkPermission(String permName, String pkgName);
    
    int checkUidPermission(String permName, int uid);
    
    boolean addPermission(in PermissionInfo info);
    
    void removePermission(String name);
    
    boolean isProtectedBroadcast(String actionName);
    
    int checkSignatures(String pkg1, String pkg2);
    
    int checkUidSignatures(int uid1, int uid2);
    
    String[] getPackagesForUid(int uid);
    
    String getNameForUid(int uid);
    
    int getUidForSharedUser(String sharedUserName);
    
    ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags);

    List queryIntentActivities(in Intent intent, 
            String resolvedType, int flags);

    List queryIntentActivityOptions(
            in ComponentName caller, in Intent[] specifics,
            in String[] specificTypes, in Intent intent,
            String resolvedType, int flags);

    List queryIntentReceivers(in Intent intent,
            String resolvedType, int flags);

    ResolveInfo resolveService(in Intent intent,
            String resolvedType, int flags);

    List queryIntentServices(in Intent intent,
            String resolvedType, int flags);

    ParceledListSlice getInstalledPackages(int flags, in String lastRead);


    ParceledListSlice getInstalledApplications(int flags, in String lastRead);


    List getPersistentApplications(int flags);

    ProviderInfo resolveContentProvider(String name, int flags);


    void querySyncProviders(inout List outNames,
            inout List outInfo);

    List queryContentProviders(
            String processName, int uid, int flags);

    InstrumentationInfo getInstrumentationInfo(
            in ComponentName className, int flags);

    List queryInstrumentation(
            String targetPackage, int flags);

    void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags,
            in String installerPackageName);

    void finishPackageInstall(int token);

 n performDexOpt(String packageName);

   
    void updateExternalMediaStatus(boolean mounted, boolean reportStatus);

    String nextPackageToClean(String lastPackage);

    void movePackage(String packageName, IPackageMoveObserver observer, int flags);
    
    boolean addPermissionAsync(in PermissionInfo info);

    boolean setInstallLocation(int loc);
    int getInstallLocation();
}

hook

public class IPackageManagerHook extends ProxyHook {

    private static final String TAG = IPackageManagerHook.class.getSimpleName();

    public IPackageManagerHook(Context hostContext) {
        super(hostContext);
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return new IPackageManagerHookHandle(mHostContext);
    }

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        Object currentActivityThread = ActivityThreadCompat.currentActivityThread();

        setOldObj(FieldUtils.readField(currentActivityThread, "sPackageManager"));
        Class iPmClass = mOldObj.getClass();
        List> interfaces = Utils.getAllInterfaces(iPmClass);
        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
        Object newPm = MyProxy.newProxyInstance(iPmClass.getClassLoader(), ifs, this);
        FieldUtils.writeField(currentActivityThread, "sPackageManager", newPm);
        PackageManager pm = mHostContext.getPackageManager();
        Object mPM = FieldUtils.readField(pm, "mPM");
        if (mPM != newPm) {
            FieldUtils.writeField(pm, "mPM", newPm);
        }
    }


    public static void fixContextPackageManager(Context context) {
        try {
            Object currentActivityThread = ActivityThreadCompat.currentActivityThread();
            Object newPm = FieldUtils.readField(currentActivityThread, "sPackageManager");
            PackageManager pm = context.getPackageManager();
            Object mPM = FieldUtils.readField(pm, "mPM");
            if (mPM != newPm) {
                FieldUtils.writeField(pm, "mPM", newPm);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

hook启动的另外一一种方式

public class InstrumentationHook extends Hook {

    private static final String TAG = InstrumentationHook.class.getSimpleName();
    private List mPluginInstrumentations = new ArrayList();

    public InstrumentationHook(Context hostContext) {
        super(hostContext);
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return null;
    }

    @Override
    public void setEnable(boolean enable, boolean reinstallHook) {
        if (reinstallHook) {
            try {
                onInstall(null);
            } catch (Throwable throwable) {
                Log.i(TAG, "setEnable onInstall fail", throwable);
            }
        }

        for (PluginInstrumentation pit : mPluginInstrumentations) {
            pit.setEnable(enable);
        }

        super.setEnable(enable,reinstallHook);
    }

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {

        Object target = ActivityThreadCompat.currentActivityThread();
        Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();

         /*替换ActivityThread.mInstrumentation,拦截组件调度消息*/
        Field mInstrumentationField = FieldUtils.getField(ActivityThreadClass, "mInstrumentation");
        Instrumentation mInstrumentation = (Instrumentation) FieldUtils.readField(mInstrumentationField, target);
        if (!PluginInstrumentation.class.isInstance(mInstrumentation)) {
            PluginInstrumentation pit = new PluginInstrumentation(mHostContext, mInstrumentation);
            pit.setEnable(isEnable());
            mPluginInstrumentations.add(pit);
            FieldUtils.writeField(mInstrumentationField, target, pit);
            Log.i(TAG, "Install Instrumentation Hook old=%s,new=%s", mInstrumentationField, pit);
        } else {
            Log.i(TAG, "Instrumentation has installed,skip");
        }
    }
}

你可能感兴趣的:(android插件化简要概述以及谈谈QQ的插件化原理大概)