基于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的插件化 的相关文章

  • 片段 onHiddenChanged 未调用

    我最近将 Fragments 添加到我的应用程序中 对于新的应用程序 我需要获得 显示我的片段后立即通知 所以我可以尽快做一些计算 片段再次显示 我的 Fragment 与 TabIndicator 一起使用 并且仅使用一个 Fragmen
  • Activity和Fragment之间如何进行回调?

    我的活动中有这个界面 public interface LogoutUser void logout 我的片段实现了这个接口 所以在我的片段中 我有这个 Override public void logout logout 在我的活动中我打
  • 如何恢复Android Fragment视图状态

    我有申请titles片段和contents屏幕上的片段 当用户单击标题片段中的项目时 将创建相应的片段并将其插入到框架中 并且所选标题在标题片段中突出显示 交易完成fragment addToBackStack 所以当用户点击BACK键 恢
  • 使用 add() 方法添加片段不会隐藏以前的片段

    使用 add 方法添加新片段时 我遇到 意外行为 我想在 FrameLayout 上添加一个新片段 但是当我这样做时 前一个片段仍然可见 这是使用 add 方法时的预期结果吗 是因为我使用的是 FrameLayout 并且 add 方法只是
  • Android 在 vi​​ewpager 中单击转到第一个片段后返回到同一片段

    我开发了一个应用程序 其中我使用片段查看寻呼机 我使用片段类将一个页面移动到另一个页面 在第二个片段类中 我有一个按钮返回 我编写编码以通过片段直接移动到第一个片段 替换 但替换第一个片段后 我不会将寻呼机页面滚动到另一个片段 这意味着当我
  • ViewPager与当前Fragment的通信

    我正在使用 ViewPager http developer android com reference android support v4 view ViewPager html http developer android com r
  • Android:Fragment的新getContext()方法是哪个上下文?

    的文档Fragment getContext says it 返回 Fragment 当前关联的上下文 在 api 23 中引入http developer android com reference android app Fragmen
  • 如何移除后退堆栈片段的焦点?

    我在我的应用程序中使用片段 我有一个片段包含EditText还有一些Dialogfragment 当我单击一个特定的小部件时 它将移动到下一个片段 我需要后台堆栈中的第一个片段 因此我还添加了 addToBackStack 方法 第二个片段
  • 针对应用程序组件工厂的清单合并失败

    我是初学者 第一次使用 MapActivity 我的项目生成了一些标题为 清单合并失败 的错误 并建议在 android 清单文件中添加一行 Manifest merger failed Attribute application appC
  • 从 DialogFragment 调用 registerReceiver

    EDIT 我添加了这一行 getActivity registerReceiver new BroadcastReceiver new IntentFilter SENT 但现在出现运行时错误 错误内容如下 09 03 04 12 41 8
  • Android getActivity() 在片段内总是返回 null

    我遇到了一个奇怪的问题 getActivity 方法always在片段内返回 null 我在 onAttach 和 onCreateView 完成运行后调用它 该片段位于包含片段堆栈的 FragmentActivity 内 我向其中添加片段
  • 如何将列表视图放入片段中

    如何将列表视图放入片段中 我使用带有固定选项卡的默认项目设置 它似乎不起作用 我的应用程序崩溃了 主要活动 import java util ArrayList import java util HashMap import java ut
  • FragmentTransaction 不执行任何操作

    我正在学习片段 下面给出的是我的第一个片段程序 一个简单的项目 我有两个屏幕 当我单击第一个屏幕的下一个按钮时 需要显示第二个按钮 我的目标是 Android 2 1 及更高版本使用兼容包 AppMainFragmentActivity j
  • ViewModelProviders 无法在我的片段中工作

    这就是我正在尝试做的事情 Set an ArrayListFragment 内的对象 从观察者处获取该数组FragmentActivity容器 承载所有片段的活动 所以 我所做的如下 首先我创建了SharedViewModel我将从哪里设置
  • FragmentActivity Junit 测试

    Me used FragmentAndroid 兼容包 使用android support v4 jar 但我无法对此进行 JUnit 测试 My main FragmentActivity类声明如下 public class MyActi
  • Fragmenttabhost 性能慢?

    我已经使用了v4 support lib for FragmentTabHost 要求是 当我将选项卡切换到另一个选项卡时 即调用 onCreateView 和 onActivityCreated 每次 这就是我的代码性能缓慢的原因 那么
  • SupportMapFragment 地图为空

    我使用以下代码在 Xamarin Android 中显示地图 private SupportMapFragment mapFragment private GoogleMap map protected override void OnCr
  • Android:使用 putExtra 从片段访问容器活动对象?

    我正在使用操作栏和片段构建选项卡界面 我需要帮助将数据从容器活动发送到片段 详细地说 我在容器活动中有作业对象 我根据工作对象中的信息创建了几个选项卡 如公司详细信息 经验详细信息等 我需要将作业对象传递给这些片段 以便它可以显示相应的信息
  • 在显示对话框时,我收到“在 onSaveInstanceState 之后无法执行此操作”

    一些用户报告说 如果他们使用通知栏中的快速操作 他们就会被强制关闭 我在通知中显示了呼叫者的快速操作 测试对话框 班级 在测试对话框类中按下 暂停 按钮后 我将显示暂停对话框 private View OnClickListener btn
  • Android 中的 Fragment-Fragment 通信

    我在Android编程方面处于初级水平 所以我需要你真诚的帮助 请任何人帮助我 我正在尝试使用片段构建滑动用户界面 所以我真正的疑问是 我有一个Fragment say FragmentA 它有一个TextView and Button在其

随机推荐

  • 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页面 第一步