基于Fragment的插件化

2023-05-16

--《摘自android插件化开发指南》

1.有些项目,整个app只有一个Activity,切换页面全靠Fragment,盛行过一时,但有点极端

2.Activity切换fragment页面

第一步:FragmentLoaderActivity作为Fragment的承载容器


<activity android:name=".FragmentLoaderActivity">
    <intent-filter>
        <action android:name="jianqiang.com.hostapp.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>  

public class BaseHostActivity extends Activity {
    private AssetManager mAssetManager;
    private Resources mResources;
    private Theme mTheme;

    protected String mDexPath;
    protected ClassLoader dexClassLoader;

    protected void loadClassLoader() {
        File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
        final String dexOutputPath = dexOutputDir.getAbsolutePath();
        dexClassLoader = new DexClassLoader(mDexPath,
                dexOutputPath, null, getClassLoader());
    }
    protected void loadResources() {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, mDexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Theme getTheme() {
        return mTheme == null ? super.getTheme() : mTheme;
    }
}  

public class FragmentLoaderActivity extends BaseHostActivity {

    private String mClass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mDexPath = getIntent().getStringExtra(AppConstants.EXTRA_DEX_PATH);
        mClass = getIntent().getStringExtra(AppConstants.EXTRA_CLASS);

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_fragment_loader);

        loadClassLoader();
        loadResources();

        try {
            //反射出插件的Fragment对象
            Class<?> localClass = dexClassLoader.loadClass(mClass);
            Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
            Object instance = localConstructor.newInstance(new Object[] {});
            Fragment f = (Fragment) instance;
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(R.id.container, f);
            ft.commit();
        } catch (Exception e) {
            Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }
}  

第二步:

MainActivity跳转到FragmentLoaderActivity,传两个参数(dexPath和fragment的名称),FragmentLoaderActivity根据参数加载对应的Fragment


Intent intent = new Intent(AppConstants.ACTION);
intent.putExtra(AppConstants.EXTRA_DEX_PATH, mPluginItems.get(position).pluginPath);
intent.putExtra(AppConstants.EXTRA_CLASS, mPluginItems.get(position).packageInfo.packageName + ".Fragment1");
startActivity(intent);  

3.插件内部的Fragment跳转


public class BaseFragment extends Fragment {
    private int containerId;

    public int getContainerId() {
        return containerId;
    }

    public void setContainerId(int containerId) {
        this.containerId = containerId;
    }
}  

public class Fragment2 extends BaseFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment2, container, false);

        if (getArguments() != null) {
            String username = getArguments().getString("username");
            TextView tv =  (TextView)view.findViewById(R.id.label);
            tv.setText(username);
        }

        view.findViewById(R.id.btnReturn).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                //从栈中将当前fragment推出
                getFragmentManager().popBackStack();
            }
        });

        return view;
    }
}  

public class Fragment1 extends BaseFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);

        view.findViewById(R.id.load_fragment2_btn).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {

                Fragment2 fragment2 = new Fragment2();
                Bundle args = new Bundle();
                args.putString("username", "baobao");
                fragment2.setArguments(args);

                getFragmentManager()
                        .beginTransaction()
                        .addToBackStack(null)  //将当前fragment加入到返回栈中
                        .replace(Fragment1.this.getContainerId(), fragment2).commit();
            }
        });

        return view;
    }
}  

其实就是利用FragmentManager动态切换Fragment技术来实现

4.插件Fragment跳转插件外部的Fragment(包括宿主中的,另一个插件中的)

第一步:把宿主和插件的资源都合并到一起,这样就能想用哪个资源就用哪个资源


public class PluginManager {
    public final static List<PluginItem> plugins = new ArrayList<PluginItem>();

    //正在使用的Resources
    public static volatile Resources mNowResources;

    //原始的application中的BaseContext,不能是其他的,否则会内存泄漏
    public static volatile Context mBaseContext;

    //ContextImpl中的LoadedAPK对象mPackageInfo
    private static Object mPackageInfo = null;

    public static void init(Application application) {
        //初始化一些成员变量和加载已安装的插件
        mPackageInfo = RefInvoke.getFieldObject(application.getBaseContext(), "mPackageInfo");

        mBaseContext = application.getBaseContext();
        mNowResources = mBaseContext.getResources();

        try {
            AssetManager assetManager = application.getAssets();
            String[] paths = assetManager.list("");

            ArrayList<String> pluginPaths = new ArrayList<String>();
            for(String path : paths) {
                if(path.endsWith(".apk")) {
                    String apkName = path;

                    PluginItem item = generatePluginItem(apkName);
                    plugins.add(item);

                    Utils.extractAssets(mBaseContext, apkName);

                    pluginPaths.add(item.pluginPath);
                }
            }

            reloadInstalledPluginResources(pluginPaths);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static PluginItem generatePluginItem(String apkName) {
        File file = mBaseContext.getFileStreamPath(apkName);
        PluginItem item = new PluginItem();
        item.pluginPath = file.getAbsolutePath();
        item.packageInfo = DLUtils.getPackageInfo(mBaseContext, item.pluginPath);

        return item;
    }

    private static void reloadInstalledPluginResources(ArrayList<String> pluginPaths) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);


            addAssetPath.invoke(assetManager, mBaseContext.getPackageResourcePath());

            for(String pluginPath: pluginPaths) {
                addAssetPath.invoke(assetManager, pluginPath);
            }

            Resources newResources = new Resources(assetManager,
                    mBaseContext.getResources().getDisplayMetrics(),
                    mBaseContext.getResources().getConfiguration());

            RefInvoke.setFieldObject(mBaseContext, "mResources", newResources);
            //这是最主要的需要替换的,如果不支持插件运行时更新,只留这一个就可以了
            RefInvoke.setFieldObject(mPackageInfo, "mResources", newResources);

            //清除一下之前的resource的数据,释放一些内存
            //因为这个resource有可能还被系统持有着,内存都没被释放
            //clearResoucesDrawableCache(mNowResources);

            mNowResources = newResources;
            //需要清理mtheme对象,否则通过inflate方式加载资源会报错
            //如果是activity动态加载插件,则需要把activity的mTheme对象也设置为null
            RefInvoke.setFieldObject(mBaseContext, "mTheme", null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}  

第二步:把所有的插件的ClassLoader都放进一个集合MyClassLoaders,在FragmentLoaderActivity中,使用MyClassLoaders来加载相应插件的Fragment


public class MyClassLoaders {
    public static final HashMap<String, DexClassLoader> classLoaders = new HashMap<String, DexClassLoader>();
}  

public class FragmentLoaderActivity extends Activity {
    private DexClassLoader classLoader;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //String pluginName = getIntent().getStringExtra(AppConstants.EXTRA_PLUGIN_NAME);
        String mClass = getIntent().getStringExtra(AppConstants.EXTRA_CLASS);
        String mDexPath = getIntent().getStringExtra(AppConstants.EXTRA_DEX_PATH);

        classLoader = MyClassLoaders.classLoaders.get(mDexPath);

        super.onCreate(savedInstanceState);

        FrameLayout rootView = new FrameLayout(this);
        rootView.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        rootView.setId(android.R.id.primary);
        setContentView(rootView);

        BaseFragment fragment = null;
        try {
            if(classLoader == null) {
                fragment = (BaseFragment) getClassLoader().loadClass(mClass).newInstance();
            } else {
                fragment = (BaseFragment) classLoader.loadClass(mClass).newInstance();
            }

            fragment.setContainerId(android.R.id.primary);
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            ft.replace(android.R.id.primary, fragment);
            ft.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Resources getResources() {
        return PluginManager.mNowResources;
    }
}  

fragment插件化的好处避开了Activity必须要面对AMS的尴尬

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于Fragment的插件化 的相关文章

  • 如何恢复Android Fragment视图状态

    我有申请titles片段和contents屏幕上的片段 当用户单击标题片段中的项目时 将创建相应的片段并将其插入到框架中 并且所选标题在标题片段中突出显示 交易完成fragment addToBackStack 所以当用户点击BACK键 恢
  • 片段和处理方向变化

    我有一个活动 其中有一个片段 我想自己处理方向变化 所以我更新了清单 如下所示
  • Android 在 vi​​ewpager 中单击转到第一个片段后返回到同一片段

    我开发了一个应用程序 其中我使用片段查看寻呼机 我使用片段类将一个页面移动到另一个页面 在第二个片段类中 我有一个按钮返回 我编写编码以通过片段直接移动到第一个片段 替换 但替换第一个片段后 我不会将寻呼机页面滚动到另一个片段 这意味着当我
  • 导航组件防止在后按时重新创建片段

    我在项目中使用 Jetpack 导航组件 其中包含单个活动和一些片段 我有一个带有从服务器端填充的列表的片段 我打电话getDataFromServer on the onViewCreated然后 当用户单击某个项目时 会显示一个新片段
  • 在 eclipse 中调试 Android 返回堆栈

    有没有办法在 Eclipse ADT IDE 中可视化 Android 中的后台堆栈以及活动和片段 您的意思是只是为了调试目的看看它是什么样子吗 在这种情况下 定义 public static void displayBackStack F
  • android - 你是否需要将片段添加到清单中

    我使用一个应该显示网络视图的片段 当我尝试从使用它的类实例化它时 我在 logcat 中收到以下警告 02 21 23 26 46 843 W System err 32468 android content ActivityNotFoun
  • 在 ViewPager 中多次使用一个 Fragment

    是否可以在视图分页器中多次使用一个片段 我正在尝试使用 ViewPager 构建动态更新的 UI 我想使用相同的设计 基本上是相同的片段 每个页面都有不同的数据 就像列表视图适配器一样 您可以为 ViewPager 中的每个页面实例化相同的
  • 片段中的片段不刷新

    这是我的应用程序 带有片段 左侧有一个ListView 您可以在其中进行选择 如果您选择了右侧之一 则加载一个片段并将选项卡添加到 ActionBar 有这样的代码 import java util ArrayList import and
  • 为什么我想要 `setRetainInstance(false)`? - 或 - 处理设备旋转的正确方法

    如果我对此有任何错误 请纠正我 这是一种澄清问题 因为我还没有在任何地方看到它明确写过 在Android 4中 您可以调用setRetainInstance true on a Fragment这样在配置更改时 这基本上意味着设备旋转 Fr
  • 交换卡片时无法更新片段文本

    我正在开发卡片刷卡和卡片翻转功能 并且我正在使用 ViewPager 和片段 我的问题是 当我从左到右或从右到左滑动卡片时 我无法更新片段内的 TextView 但是当我翻转卡片时 它会更新 UI 我尝试了互联网上可用的所有解决方案 但没有
  • “setHasOptionsMenu(Boolean): Unit”已弃用。在 Java 中已弃用

    如何在 Android 片段中声明菜单 我以前使用的方法现在已被弃用 起初 override fun onCreateView View setHasOptionsMenu true override fun onCreateOptions
  • ViewModelProviders 无法在我的片段中工作

    这就是我正在尝试做的事情 Set an ArrayListFragment 内的对象 从观察者处获取该数组FragmentActivity容器 承载所有片段的活动 所以 我所做的如下 首先我创建了SharedViewModel我将从哪里设置
  • 当我们回来时,查看寻呼机片段状态寻呼机适配器出现白屏?

    我已经使用 FragmentStatePagerAdapter 使用视图分页器来加载片段 当我第一次来时 它会工作 但如果我从寻呼机适配器重定向到其他片段并返回 它将显示空白屏幕 fragment community xml
  • 从底部工作表对话框片段中获取值

    我从片段A开始bottomSheetDialogFragment 我想从该bottomSheetDialogFragment中选择日期 然后将其设置在片段A中 选择日期已经完成 我只想将其获取到片段A中以在某些字段中设置它 我怎样才能得到这
  • 是否可以仅使用一个实例来创建片段

    我只是想知道 片段创建只能有一个实例或单例吗 我经历了谷歌iosched项目也 他们只是简单地创造 Fragment a new Fragment 每当他们想要 假设例如 public static FragmentManager inst
  • Android 4.4 - 半透明状态/导航栏 - FitsSystemWindows/clipToPadding 无法通过片段事务工作

    当使用新的 Android 4 4 KitKat API 中的半透明状态栏和导航栏时 设置fitsSystemWindows true and clipToPadding false to a ListView最初起作用 fitsSyste
  • popBackStack导致反复调用fragment的oncreateView

    我有 3 个片段 A B C 我编写了一段代码来替换它们并维护 backstack public void addFragment Fragment fragmentToAdd String fragmentTag FragmentMana
  • 从 Activity 更新片段

    我不明白如何将数据从fragmentactivity传输到fragment 我有 主屏幕 类 public class MainScreen extends FragmentActivity CollectionPagerAdapter m
  • setUserVisibleHint 中的空上下文

    当 ViewPager 中的片段变得可见时 需要向用户显示一条消息 目前的通话是 Within a class that extends Fragment Override public void setUserVisibleHint bo
  • Android:RecyclerView 不显示片段中的列表项

    有人可以帮我尝试让我的 RecyclerView 出现吗 如果我不在片段中实现它 就会出现这种情况 然而 当我尝试将其实现到片段中时 CarFront 中的其他 XML 代码与 RecyclerView 分开显示 我的日志中收到此错误 E

随机推荐

  • java在数字前面自动补零的方法

    将元数据前补零 xff0c 补后的总长度为指定的长度 xff0c 以字符串的形式返回 64 param sourceDate 64 param formatLength 64 return 重组后的数据 public static Stri
  • MariaDB命令详解

    MariaDB命令详解 mysql客户端程序 xff1a 命令行交互式客户端程序 xff1a mysql mysql mysql OPTIONS database mysql help 配置文件的读取次序 xff1a etc mysql m
  • python文本文件操作诗句给上一句输出下一句_[Python] 自动化办公 定制微信每日一句诗...

    转载请注明 xff1a 陈熹 chenx6542 64 foxmail com 简书号 xff1a 半为花间酒 若公众号内转载请联系公众号 xff1a 早起Python 这篇文章能学到的主要内容 xff1a 利用 喵提醒 推送消息至微信 x
  • 我的年终总结,作为研发,在2018年都有哪些进步、收获与成长?

    2018 结束了 部门开会总结了过去的工作与未来的展望 xff0c 也是个不错的机会去回顾 审视 思考自己的 2018 年 玄难说过人与人的差距来自于思考与总结 xff0c 我深深地认同这一点 我也把自己的一部分思考写下来 xff0c 在公
  • Arch无法更新和安装软件

    今天用户yay明来安装htop时 xff0c 一直卡死在以下输出内容出 xff1a db lck is present Waiting 更新软件源也出现以下故障 xff1a sudo pacman Syy sudo ivan 的密码 xff
  • 云主机的硬盘IO性能比较

    测试方式 因为工作等需要 xff0c 手里有一堆云主机 xff0c 前几天忽然想到来测试对比一下各家的IO性能如何 测试方法不严谨 xff0c 仅供参考 测试工具为fio xff0c 测试命令如下 xff08 以sync方式为例 xff09
  • 定制小狼豪(五笔+拼音)输入法

    小狼毫输入法是一个给程序员折腾的输入法 xff0c 可以自由定制 rime是一个输入法框架 xff0c 小狼毫是在windows平台上的名称 相关教程和下载 xff1a https jianguoyun com p DRylhFMQv 3j
  • 10.12 firewalld和netfilter

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Linux防火墙 netfilter selinux临时关闭 setenforce 0selinux永久关闭 vi etc selinux configcentos7之前使
  • 使用 build-simple-cdd 快速定制 Debian 安装盘

    为什么80 的码农都做不了架构师 xff1f gt gt gt 官方推荐了 build simple cdd 来 定制Debian安装盘 sudo apt get y install simple cdd xorriso 创建基础目录和文件
  • PostSharp-5.0.26安装包_KeyGen发布_支持VS2017

    PostSharp 5 0 26安装包 KeyGen发布 支持VS2017 请低调使用 PostSharp安装及注册步骤截图 rar 请把浏览器主页设置为以下地址支持本人 https www duba com un 454974 16968
  • centos7 Firewall防火墙开启80端口

    为什么80 的码农都做不了架构师 xff1f gt gt gt centos7 默认是FirewallD 提供支持网络 防火墙区域 zone 定义网络链接以及接口安全等级的动态防火墙管理工具 xff0c 利用FirewallD开启80端口操
  • 安卓6.0系统权限问题android.permission.WRITE_SETTINGS

    关于 Android permission WRITE SETTINGS 的权限 xff0c 申请 xff0c 判断 精简代码如下 xff1a if Build VERSION SDK INT gt 61 Build VERSION COD
  • js match函数注意

    match函数 String prototype match 参数 regexp 返回 返回包含所有匹配的数组 xff0c 如果匹配失败返回Null 数组第一项是整段字符串的匹配 xff0c 第二项至以后都是捕获匹配 注意 需要注意的是 x
  • VR发展简史

    最初的起源 事实上 xff0c 虚拟现实由来已久 xff0c 其概念最早被提及应该追溯到Aldous Huxley xff08 阿道司 赫胥黎 xff09 1932年推出的长篇小说 美丽新世界 xff0c 这篇小说以26世纪为背景 xff0
  • crontab 每月执行一次怎么写? - Linux系统管理 - ChinaUnix.net -

    crontab 每月执行一次怎么写 xff1f Linux系统管理 ChinaUnix net 0 19 1 bin sh xxx sh 每个月的1号的19点钟运行xxx sh 分钟 小时 日子可以更改 xff0c 后两项为 就是month
  • SparkStreaming结合Kafka使用

    spark自带的example中就有streaming结合kafka使用的案例 xff1a SPARK HOME examples src main scala org apache spark examples streaming Kaf
  • grails一对多双向关联

    前面分享了一些学习grails的心得 xff0c 可是grails的知识还远不止这些 xff0c 这次整理了一点有关grails一对多双向关联关系的知识 我认为这样的关联用的地方太多了 xff0c 这次准备的样例是城市和区域的相关样例 1
  • IAR EWAR 内联汇编 调用外部函数 Error[Og005], Error[Og006]

    How do I call a C function in another module from inline assembler in IAR EWARM I have a bit of assembly in a hard fault
  • GOEXIF读取和写入EXIF信息

    最新版本的gexif xff0c 直接基于gdi 43 实现了exif信息的读取和写入 xff0c 代码更清晰 File gexif h Purpose cpp EXIF reader 3 2 2017 lt jsxyhelu 64 fox
  • 基于Fragment的插件化

    摘自android插件化开发指南 1 有些项目 xff0c 整个app只有一个Activity xff0c 切换页面全靠Fragment xff0c 盛行过一时 xff0c 但有点极端 2 Activity切换fragment页面 第一步