Android Open Source - AndroidPlugin Plugin Manager






From Project

Back to project page AndroidPlugin.

License

The source code is released under:

MIT License

If you think the Android project AndroidPlugin listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2015 HouKx <hkx.aidream@gmail.com>
 *//from   w  ww.  j a v a  2  s .com
 * 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 androidx.pluginmgr;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import android.app.Activity;
import android.app.Application;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;

/**
 * ?????????
 * 
 * @author HouKangxi
 */
public class PluginManager implements FileFilter {
    private static final PluginManager instance = new PluginManager();
    private Activity actFrom;

    private PluginManager() {
    }

    public static PluginManager getInstance(Context context) {
        if (instance.hasInit || context == null) {
            return instance;
        }
        Context ctx = context;
        if (context instanceof Activity) {
            instance.actFrom = (Activity) context;
            ctx = ((Activity) context).getApplication();
        } else if (context instanceof Service) {
            ctx = ((Service) context).getApplication();
        } else if (context instanceof Application) {
            ctx = (Application) context;
        } else {
            ctx = context.getApplicationContext();
        }
        synchronized (PluginManager.class) {
            instance.init(ctx);
        }
        return instance;
    }

    static PluginManager getInstance() {
        return instance;
    }

    public boolean startMainActivity(Context context, String pkgOrId) {
        Log.d(tag, "startMainActivity by:" + pkgOrId);
        PlugInfo plug = preparePlugForStartActivity(context, pkgOrId);
        if (frameworkClassLoader == null) {
            Log.e(tag, "startMainActivity: frameworkClassLoader == null!");
            return false;
        }
        if (plug.getMainActivity() == null) {
            Log.e(tag, "startMainActivity: plug.getMainActivity() == null!");
            return false;
        }
        if (plug.getMainActivity().activityInfo == null) {
            Log.e(tag,
                    "startMainActivity: plug.getMainActivity().activityInfo == null!");
            return false;
        }
        String className = frameworkClassLoader.newActivityClassName(
                plug.getId(), plug.getMainActivity().activityInfo.name);
        context.startActivity(new Intent().setComponent(new ComponentName(
                context, className)));
        return true;
    }

    public void startActivity(Context context, Intent intent) {
        performStartActivity(context, intent);
        context.startActivity(intent);
    }

    public void startActivityForResult(Activity activity, Intent intent,
            int requestCode) {
        performStartActivity(context, intent);
        activity.startActivityForResult(intent, requestCode);
    }

    private PlugInfo preparePlugForStartActivity(Context context,
            String plugIdOrPkg) {
        PlugInfo plug = null;
        plug = getPluginByPackageName(plugIdOrPkg);
        if (plug == null) {
            plug = getPluginById(plugIdOrPkg);
        }
        if (plug == null) {
            throw new IllegalArgumentException("plug not found by:"
                    + plugIdOrPkg);
        }
        return plug;
    }

    private void performStartActivity(Context context, Intent intent) {
        checkInit();

        String plugIdOrPkg;
        String actName;
        ComponentName origComp = intent.getComponent();
        if (origComp != null) {
            plugIdOrPkg = origComp.getPackageName();
            actName = origComp.getClassName();
        } else {
            throw new IllegalArgumentException(
                    "plug intent must set the ComponentName!");
        }
        PlugInfo plug = preparePlugForStartActivity(context, plugIdOrPkg);
        String className = frameworkClassLoader.newActivityClassName(
                plug.getId(), actName);
        Log.i(tag, "performStartActivity: " + actName);
        ComponentName comp = new ComponentName(context, className);
        intent.setAction(null);
        intent.setComponent(comp);
    }

    private final Map<String, PlugInfo> pluginIdToInfoMap = new ConcurrentHashMap<String, PlugInfo>();
    private final Map<String, PlugInfo> pluginPkgToInfoMap = new ConcurrentHashMap<String, PlugInfo>();
    private Context context;
    private String dexOutputPath;
    private volatile boolean hasInit = false;
    private File dexInternalStoragePath;
    private FrameworkClassLoader frameworkClassLoader;
    private PluginActivityLifeCycleCallback pluginActivityLifeCycleCallback;
    private static final String tag = "plugmgr";

    private void init(Context ctx) {
        context = ctx;
        File optimizedDexPath = ctx.getDir("plugsout", Context.MODE_PRIVATE);
        if (!optimizedDexPath.exists()) {
            optimizedDexPath.mkdirs();
        }
        dexOutputPath = optimizedDexPath.getAbsolutePath();
        dexInternalStoragePath = context
                .getDir("plugins", Context.MODE_PRIVATE);
        dexInternalStoragePath.mkdirs();
        try {
            Object mPackageInfo = ReflectionUtils.getFieldValue(ctx,
                    "mBase.mPackageInfo", true);
            frameworkClassLoader = new FrameworkClassLoader(
                    ctx.getClassLoader());
            // set Application's classLoader to FrameworkClassLoader
            ReflectionUtils.setFieldValue(mPackageInfo, "mClassLoader",
                    frameworkClassLoader, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        hasInit = true;
    }

    private void checkInit() {
        if (!hasInit) {
            throw new IllegalStateException("PluginManager has not init!");
        }
    }

    public PlugInfo getPluginById(String pluginId) {
        if (pluginId == null) {
            return null;
        }
        return pluginIdToInfoMap.get(pluginId);
    }

    public PlugInfo getPluginByPackageName(String packageName) {
        return pluginPkgToInfoMap.get(packageName);
    }

    public Collection<PlugInfo> getPlugins() {
        return pluginIdToInfoMap.values();
    }

    public void uninstallPluginById(String pluginId) {
        uninstallPlugin(pluginId, true);
    }

    public void uninstallPluginByPkg(String pkg) {
        uninstallPlugin(pkg, false);
    }

    private void uninstallPlugin(String k, boolean isId) {
        checkInit();
        PlugInfo pl = isId ? removePlugById(k) : removePlugByPkg(k);
        if (pl == null) {
            return;
        }
        if (context instanceof Application) {
            if (android.os.Build.VERSION.SDK_INT >= 14) {
                try {
                    Application.class
                            .getMethod(
                                    "unregisterComponentCallbacks",
                                    Class.forName("android.content.ComponentCallbacks"))
                            .invoke(context, pl.getApplication());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private PlugInfo removePlugById(String pluginId) {
        PlugInfo pl = null;
        synchronized (this) {
            pl = pluginIdToInfoMap.remove(pluginId);
            if (pl == null) {
                return null;
            }
            pluginPkgToInfoMap.remove(pl.getPackageName());
        }
        return pl;
    }

    private PlugInfo removePlugByPkg(String pkg) {
        PlugInfo pl = null;
        synchronized (this) {
            pl = pluginPkgToInfoMap.remove(pkg);
            if (pl == null) {
                return null;
            }
            pluginIdToInfoMap.remove(pl.getId());
        }
        return pl;
    }

    /**
     * ?????????????????????
     * <p>
     * ??????????Id
     * 
     * @param pluginSrcDirFile
     *            - apk?apk??
     * @return ????????
     * @throws Exception
     */
    public Collection<PlugInfo> loadPlugin(final File pluginSrcDirFile)
            throws Exception {
        checkInit();
        if (pluginSrcDirFile == null || !pluginSrcDirFile.exists()) {
            Log.e(tag, "invalidate plugin file or Directory :"
                    + pluginSrcDirFile);
            return null;
        }
        if (pluginSrcDirFile.isFile()) {
            // ???????????????????????????????apk?????????????????????,?jar
            PlugInfo one = loadPluginWithId(pluginSrcDirFile, null, null);
            return Collections.singletonList(one);
        }
        // clear all first
        synchronized (this) {
            pluginPkgToInfoMap.clear();
            pluginIdToInfoMap.clear();
        }
        File[] pluginApks = pluginSrcDirFile.listFiles(this);
        if (pluginApks == null || pluginApks.length < 1) {
            throw new FileNotFoundException("could not find plugins in:"
                    + pluginSrcDirFile);
        }
        for (File pluginApk : pluginApks) {
            PlugInfo plugInfo = buildPlugInfo(pluginApk, null, null);
            if (plugInfo != null) {
                savePluginToMap(plugInfo);
            }
        }
        return pluginIdToInfoMap.values();
    }

    private synchronized void savePluginToMap(PlugInfo plugInfo) {
        pluginPkgToInfoMap.put(plugInfo.getPackageName(), plugInfo);
        pluginIdToInfoMap.put(plugInfo.getId(), plugInfo);
    }

    // /**
    // * ????????apk <br/>
    // * ?????????????id <br/>
    // * ?????????????????
    // *
    // * @param pluginApk
    // * @return
    // * @throws Exception
    // */
    // public PlugInfo loadPlugin(File pluginApk) throws Exception {
    // return loadPluginWithId(pluginApk, null, null);
    // }

    /**
     * ????????apk
     * 
     * @param pluginApk
     * @param pluginId
     *            - ???????null,??????????????id
     * @return
     * @throws Exception
     */
    public PlugInfo loadPluginWithId(File pluginApk, String pluginId)
            throws Exception {
        return loadPluginWithId(pluginApk, pluginId, null);
    }

    public PlugInfo loadPluginWithId(File pluginApk, String pluginId,
            String targetFileName) throws Exception {
        checkInit();
        PlugInfo plugInfo = buildPlugInfo(pluginApk, pluginId, targetFileName);
        if (plugInfo != null) {
            savePluginToMap(plugInfo);
        }
        return plugInfo;
    }

    private PlugInfo buildPlugInfo(File pluginApk, String pluginId,
            String targetFileName) throws Exception {
        PlugInfo info = new PlugInfo();
        info.setId(pluginId == null ? pluginApk.getName() : pluginId);

        File privateFile = new File(dexInternalStoragePath,
                targetFileName == null ? pluginApk.getName() : targetFileName);

        info.setFilePath(privateFile.getAbsolutePath());

        if (!pluginApk.getAbsolutePath().equals(privateFile.getAbsolutePath())) {
            copyApkToPrivatePath(pluginApk, privateFile);
        }
        String dexPath = privateFile.getAbsolutePath();
        PluginManifestUtil.setManifestInfo(context, dexPath, info);

        PluginClassLoader loader = new PluginClassLoader(dexPath,
                dexOutputPath, frameworkClassLoader, info);
        info.setClassLoader(loader);

        try {
            AssetManager am = (AssetManager) AssetManager.class.newInstance();
            am.getClass().getMethod("addAssetPath", String.class)
                    .invoke(am, dexPath);
            info.setAssetManager(am);
            Resources ctxres = context.getResources();
            Resources res = new Resources(am, ctxres.getDisplayMetrics(),
                    ctxres.getConfiguration());
            info.setResources(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
        initPluginApplication(info);
        // createPluginActivityProxyDexes(info);
        Log.i(tag, "buildPlugInfo: " + info);
        return info;
    }

    // private void createPluginActivityProxyDexes(PlugInfo plugin) {
    // {
    // ActInfo act = plugin.getApplicationInfo().getMainActivity();
    // if (act != null) {
    // ActivityOverider.createProxyDex(plugin, act.name, false);
    // }
    // }
    // if (plugin.getApplicationInfo().getOtherActivities() != null) {
    // for (ActInfo act : plugin.getApplicationInfo().getOtherActivities())
    // {
    // ActivityOverider.createProxyDex(plugin, act.name, false);
    // }
    // }
    // }

    private void initPluginApplication(final PlugInfo info) throws Exception {
        String className = info.getPackageInfo().applicationInfo.name;
        Log.d(tag, info.getId() + ", ApplicationClassName = " + className);
        // create Application instance for plugin
        if (className == null) {
            Application application = new Application();
            setApplicationBase(info, application);
        } else {
            ClassLoader loader = info.getClassLoader();
            final Class<?> applicationClass = loader.loadClass(className);
            if (actFrom != null) {
                actFrom.runOnUiThread(new Runnable() {
                    public void run() {
                        try {
                            Application application = (Application) applicationClass
                                    .newInstance();
                            setApplicationBase(info, application);
                            // invoke plugin application's onCreate()
                            application.onCreate();
                        } catch (Throwable e) {
                            Log.e(tag, Log.getStackTraceString(e));
                        }
                    }
                });

            } else {
                Application application = (Application) applicationClass
                        .newInstance();
                setApplicationBase(info, application);
            }
        }
    }

    private void setApplicationBase(PlugInfo info, Application application)
            throws Exception {
        info.setApplication(application);
        //
        PluginContextWrapper ctxWrapper = new PluginContextWrapper(context,
                info);
        // attach
        java.lang.reflect.Method attachMethod = android.app.Application.class
                .getDeclaredMethod("attach", Context.class);
        attachMethod.setAccessible(true);
        attachMethod.invoke(application, ctxWrapper);
        if (context instanceof Application) {
            if (android.os.Build.VERSION.SDK_INT >= 14) {
                Application.class.getMethod("registerComponentCallbacks",
                        Class.forName("android.content.ComponentCallbacks"))
                        .invoke(context, application);
            }
        }
    }

    private void copyApkToPrivatePath(File pluginApk, File f) {
        // if (f.exists() && pluginApk.length() == f.length()) {
        // // ??????????????????????????????????????????????? md5\sha-1
        // return;
        // }
        FileUtil.copyFile(pluginApk, f);
    }

    File getDexInternalStoragePath() {
        return dexInternalStoragePath;
    }

    Context getContext() {
        return context;
    }

    public PluginActivityLifeCycleCallback getPluginActivityLifeCycleCallback() {
        return pluginActivityLifeCycleCallback;
    }

    public void setPluginActivityLifeCycleCallback(
            PluginActivityLifeCycleCallback pluginActivityLifeCycleCallback) {
        this.pluginActivityLifeCycleCallback = pluginActivityLifeCycleCallback;
    }

    FrameworkClassLoader getFrameworkClassLoader() {
        return frameworkClassLoader;
    }

    @Override
    public boolean accept(File pathname) {
        if (pathname.isDirectory()) {
            return false;
        }
        String fname = pathname.getName();
        return fname.endsWith(".apk");
    }

}




Java Source Code List

android.widget.ViewStub.java
androidx.plmgrdemo.MainActivity.java
androidx.plmgrdemo.PlugListViewAdapter.java
androidx.pluginmgr.ActivityClassGenerator.java
androidx.pluginmgr.ActivityOverider.java
androidx.pluginmgr.FileUtil.java
androidx.pluginmgr.FrameworkClassLoader.java
androidx.pluginmgr.LayoutInflaterWrapper.java
androidx.pluginmgr.PlugInfo.java
androidx.pluginmgr.PluginActivityLifeCycleCallback.java
androidx.pluginmgr.PluginClassLoader.java
androidx.pluginmgr.PluginContextWrapper.java
androidx.pluginmgr.PluginManager.java
androidx.pluginmgr.PluginManifestUtil.java
androidx.pluginmgr.ReflectionUtils.java
androidx.pluginmgr.XmlManifestReader.java
com.limemobile.app.demo.pluginclienta.ClientABindService.java
com.limemobile.app.demo.pluginclienta.ClientAStartedService.java
com.limemobile.app.demo.pluginclienta.MainActivity.java
com.limemobile.app.demo.pluginclienta.TestButton.java
com.limemobile.app.demo.pluginclienta.TestFragmentActivity.java
com.limemobile.app.demo.pluginclienta.TestFragment.java
com.limemobile.app.demo.pluginclientb.ClientBStartedService.java
com.limemobile.app.demo.pluginclientb.MainActivity.java
com.limemobile.app.demo.pluginhost.HostBindService.java
com.limemobile.app.demo.pluginhost.HostStartedService.java
com.limemobile.app.demo.pluginhost.MainActivity.java
com.limemobile.app.demo.pluginhost.MyApplication.java
com.limemobile.app.demo.pluginhost.TestHostClass.java
com.limemobile.app.plugin.IPluginActivity.java
com.limemobile.app.plugin.IPluginContentProvider.java
com.limemobile.app.plugin.IPluginService.java
com.limemobile.app.plugin.PluginClientActivity.java
com.limemobile.app.plugin.PluginClientFragmentActivity.java
com.limemobile.app.plugin.PluginClientService.java
com.limemobile.app.plugin.PluginHostApplication.java
com.limemobile.app.plugin.PluginHostContentProvider.java
com.limemobile.app.plugin.PluginHostDelegateActivity.java
com.limemobile.app.plugin.PluginHostDelegateContentProvider.java
com.limemobile.app.plugin.PluginHostDelegateFragmentActivity.java
com.limemobile.app.plugin.PluginHostDelegateService.java
com.limemobile.app.plugin.internal.IPluginActivityDelegate.java
com.limemobile.app.plugin.internal.IPluginContentProviderDelegate.java
com.limemobile.app.plugin.internal.IPluginServiceDelegate.java
com.limemobile.app.plugin.internal.PluginClientDexClassLoader.java
com.limemobile.app.plugin.internal.PluginClientInfo.java
com.limemobile.app.plugin.internal.PluginClientManager.java
com.limemobile.app.plugin.internal.PluginDelegateActivityImpl.java
com.limemobile.app.plugin.internal.PluginDelegateContentProviderImpl.java
com.limemobile.app.plugin.internal.PluginDelegateServiceImpl.java
com.limemobile.app.plugin.internal.ReflectFieldAccessor.java