文件管理器很庞大,这几年在开源 nemo 的基础上,也算是解决了不少内部测试报出来的问题,但是对它的认识一直都很片面,并不能从整体上对它有个完整的认识。下面几个还算比较熟悉:
窗口左边栏 NemoPlacesSidebar
窗口视图和右键菜单 NemoView及其子类
窗口容器 NemoIconContainer
计算机页面 computer:/// 的显示
窗口面板和地址栏 NemoWindowPane、NemoLocationEntry
插件机制
以上这几个都还只是 GUI 显示,没有涉及到 Nemo 的 I/O。文件管理器分为两大部分,用户交互的 GUI 部分,是GTK 开发的,显示的实际数据(也就是文件),是通过 gio/glib 库拿到的。写这边文章的目的是希望能好好梳理整体流程,将双击目录图标,请求数据,显示图标名称的整个过程描述清楚。
以打开计算机页面为例,希望能梳理出从我双击计算机图标到提取文件信息,再到请求 computer:/// 中所有文件的图标、名称、大小等,最终把图标文件名等显示在视图中的一整套过程。增加对下面这几个对象的认识
NemoFile NemoDirectory
NemoIconInfo
EelCanvasItem
Nemo 的 GUI 部分
GUI 包括工具栏、状态栏、左边栏和视图区,其中视图分为两大类:图标视图和列表视图,而图标视图又分为大、中、小图标和紧凑视图。这部分代码基本都在 src 目录下。
图标视图中所有显示出来的文件目录等,数据内容存在 NemoView 或者子类 NemoIconView,交互事件响应,图标位置的更新等在 NemoIconContainer。容器 NemoIconContainer 中最重要的就是 NemoIconCanvasItem。这部分代码基本都在 libnemo-private。
NemoIconView 和 NemoIconContainer
NemoIconView 是图标视图,与列表视图不一样,列表视图其实是 GTK 的控件,所以它不需要 NemoIconContainer 容器,紧凑视图、大中小三个图标视图都是 NemoIconView,这类视图对象有个成员是 NemoIconContainer,通过它来响应用户双击、选中、右键以及拷贝删除容器类中对象的各个信号。比如有个很神奇的功能,这次分析代码才发现,如果 click-double-parent-folder 设置为 true,双击图标视图的空白处,可以回到父目录,这个功能在 NemoIconView 中通过响应 NemoIconContainer 的 button_press_event 信号来实现。
Nemo 的 I/O 部分
NemoFile 和 NemoDirectory
NemoFile 主要用途:封装了调度异步 I/O 的功能。
NemoFile 提供了一系列异步I/O的API 来应对文件信息尚未确定的情况。就比如通过 nemo_file_get 接口拿到某个文件和目录结构(注意此时不会有任何I/O),我们想知道这个文件的类型 nemo_file_get_file_type,但是对于新建的文件很可能一开始还并不能确定类型是什么,这种情况该怎么处理?
用 nemo_file_monitor_add 和 nemo_file_call_when_ready。
比如加载目录时,我们表示想要知道属性
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
该怎么做?
答案是通过在 load_directory 中调用函数加关注,
nemo_file_monitor_add (view->details->directory_as_file,
&view->details->directory_as_file,
attributes);
当这几个属性准备好了,就会自动去执行call_when_ready 的回调:
nemo_file_call_when_ready
(view->details->directory_as_file,
attributes,
metadata_for_directory_as_file_ready_callback, view);
一旦我们调用了 nemo_file_monitor_add 就表示关联了 NemoFile 的 changed 信号,nemo-monitor.c 中
g_signal_connect (ret->monitor, "changed",
G_CALLBACK (dir_changed), ret);
这个 ret->monitor 成员就是一个 GFileMonitor 对象。所以我们不需要再去 connect 文件的 changed 信号,一旦属性发生了变化,nemo_file_call_when_ready 注册的回调函数就会自动被调用。(不管是 monitor 还是 callback 都是可以取消的)
on an ongoing basis 持续进行的
Something which occurs on an ‘ongoing basis’ is something which is occurring and which will continue to occur, without a definite ending. The sun rises and the sun sets ‘on an on-going basis’. Your boss expects you to show up five days a week at 8 AM on an ‘on-going basis’.
2、NemoDirectory 用于管理一组文件,当目录内任何一个文件修改了,都会有 files_changed 信号,所以我们不需要像上面操作 NemoFile 一样挨个去 monitor,去 callback when ready。如果我们想要一次性操作目录下的所有文件,只需要 NemoDirectory对象,在这个对象里面我们可以一次性监控某个目录下的所有 NemoFile 对象,只要我们监听一个 files_changed 信号,当目录下任意一个文件被修改了都能感应到,而不需要对每个文件都添加 monitor,可以省很多事情。
从文件管理器角度来说,用户点击某个文件夹图标,或者在地址栏输入某个 uri,回车跳转到对应的地址,这些UI层的操作,执行到最后,都是从文件系统中去获取文件,它使用的是 gio-2.0 库的接口。
nemo 调用 gio 库,实际上是 gvfs 实现出来的。 glib 中定义接口GFileIface,并提供接口方法,gvfs 实现这个接口。
Glib 接口
gvfs 实现
GFileIface
GDaemonFile
g_file_enumerate_children_async
g_daemon_file_enumerate_children_async
g_file_enumerator_next_files_finish
g_daemon_file_enumerator_next_files_finish
……
……
GFile 接口主要用于操作虚拟文件系统上文件和目录的一个高层抽象,并不代表实际的文件,文件内容那些的操作都交给 GInputStream、GOutputStream。
总结在文件管理器中打开一个文件窗口的各种方式,比如通过书签的方式打开
activate_bookmark_in_menu_item (参数有个 location)
location = nemo_bookmark_get_location (holder->bookmark);
通过在 entry 中输入地址回车的方式打开:
navigation_bar_location_changed_callback (参数也有个 location)
nemo_window_slot_open_location_full
上面两种方式都会进入 begin_location_change 函数,再接着往下走。在这个函数里,slot->determine_view_file = nemo_file_get (location); 用来拿到所选视图的所有信息。然后:
nemo_file_call_when_ready (slot->determine_view_file,
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT,
got_file_info_for_view_selection_callback,
slot);
在回调函数中,获取view_id,判断是哪种视图?图标还是列表?比较特殊的是 computer:/// 为了实现类似于windows 的首页,我们定制了一个全新的视图。
各种信息准备完成后,函数调用如下:
create_content_view
load_new_location
nemo_view_load_location
load_directory
3.双击文件图标打开:
双击的操作作用于 NemoIconContainer 对象,也就是调用activate_selected_items,然后发出 activate 信号。NemoIconView 收到这个信号后,调用 nemo_view_activate_files,然后就走到 nemo_mime_activate_files, NemoIconView 作为 NemoView 的子类,会向上链接执行父类的函数,也就是说 NemoView 的 load_directory 去各 uri 加载文件。
这三种方式,尽管开头不一样,但是最终都需要 load_directory 去加载目录中的文件。
梳理 Nemo 的 I/O
Nemo 的主线程上没有任何的 I/O 操作,如果主线程上有 I/O,那么用户在操作文件管理器过程中不可避免地会卡住,这种体验挺让人崩溃的,所以文件管理器在设计的时候就保证主线程不会被阻塞,也就是说不会在nemo主线程上做任何磁盘I/O操作。
Nemo的多线程通过专门的异步对象来处理,需要仔细研读代码nemo-directory-async.c,它封装了许多对 NemoDirectory 的操作,最重要的就是 nemo_directory_async_state_changed ,这个函数在非常非常频繁的被调用,他就是 Nemo I/O 操作的核心,每当I/O完成或者 monitor、call_when_reay 发生变化都会调用它。这个函数主要工作就是 start_or_stop_io。
比如查询文件信息的接口:nemo_directory_get_info_for_new_files,它会去调用 g_file_query_info_async,这个 glib 接口的具体实现在 gvfs,假如是个本地文件,接口可能在 gvfs/gdaemonfile.c 中的函数g_daemon_file_query_info_async。它会异步调用 gvfs 的 QueryInfo dbus 接口。
以上面介绍的第三种方式双击文件夹为例,双击的操作作用于 NemoIconContainer 对象,也就是调用activate_selected_items,然后发出 activate 信号。NemoIconView 收到这个信号后,调用 nemo_view_activate_files,然后就走到 nemo_mime_activate_files, NemoIconView 作为 NemoView 的子类,会向上链接执行父类的函数,也就是说 NemoView 的 load_directory 去各 uri 加载文件下来。加载时关心的属性有:
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
view->details->metadata_for_directory_as_file_pending = TRUE;
view->details->metadata_for_files_in_directory_pending = TRUE;
nemo_file_call_when_ready
(view->details->directory_as_file,
attributes,
metadata_for_directory_as_file_ready_callback, view);
nemo_directory_call_when_ready
(view->details->model,
attributes,
FALSE,
metadata_for_files_in_directory_ready_callback, view);
/* If capabilities change, then we need to update the menus
* because of New Folder, and relative emblems.
*/
attributes =
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_FILESYSTEM_INFO;
**nemo_file_monitor_add** (view->details->directory_as_file,
&view->details->directory_as_file,
attributes);
这三个属性拿到后,分别执行目录和文件的 call_when_ready 函数,这里的目录就是我们想要加载的目录本身,这里的文件同样也是这个目录,只不过作为文件而已。当目录作为目录时,它需要关心所有子文件和子目录的元数据是否完成,当目录作为文件时,只需要关心它自己的元数据有没有准备好就可以了。不管怎么说,这两个回调都会执行 finish_loading,而在这个函数里面,关心的属性
attributes =
NEMO_FILE_ATTRIBUTES_FOR_ICON |
NEMO_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT |
NEMO_FILE_ATTRIBUTE_INFO |
NEMO_FILE_ATTRIBUTE_LINK_INFO |
NEMO_FILE_ATTRIBUTE_MOUNT |
NEMO_FILE_ATTRIBUTE_EXTENSION_INFO;
比如文件图标、目录下的文件个数以及一些扩展信息等。拿到这些信息,执行回调 files_added_callback,这样走下去我们就能把它在容器中显示出来了。调用函数清单:
queue_pending_files
schedule_timeout_display_of_pending_files 开始逐步加载文件
display_pending_callback
display_pending_files
process_new_files 和 process_old_files
在process_old_files 函数中遍历 files_added 链表,发出 add_file 信号,关联子类的 *_add_file 回调,比如 nemo_icon_view_add_file,最后调用 nemo_icon_container_add 将图标显示出来。
在这个流程中,如果文件内容比较多,不能马上加载完成,加载时会在窗口右下角展示正在加载的转圈( finish_loading)。这里涉及到一个设计模式,MVC, NemoIconView 顾名思义,就是视图;M 其实是 NemoDirectory,这个对象负责数据的加载。NemoDirectory 发出 DONE_LOADING 的信号,此时才是所有文件都加载完成了。
另外,需要注意的是这个函数 nemo_directory_async_state_changed,注释说每当 monitor 或者 call_when_ready 列表发生变化,又或者每个 I/O 活动完成,比如 query_info_callback,都会调用这个函数。我们姑且认为,在 nemo_file_monitor_add 和那两个 call_when_ready 函数首先触发了这个函数。这个函数里面,开始了及其重要的 start_or_stop_io 函数。在这里我们大概能看出来有多少 I/O,比如 file-list、file-info、directory-count、deep-count、mime-list、thumbnail、filesystem-info、extension-info 等等。
以 file-list 为例,start_monitoring_file_list 调用 g_file_enumerate_children_asyc,这个 async 的回调 enumerate_children_callback 调用 g_file_enumerator_next_files_finish, 对应上一个 async。并且调用 g_file_enumerator_next_files_async, 它的 more files callback 再去调用 g_file_enumerator_next_files_finish。
这些都是一些很典型的 gio/glib 函数, _async 表示开启异步操作,_finish 表示拿到结果。从文件管理器角度来说,所以加载过程都是异步执行的。nemo 拿到数据后(包括需要渲染到 GTK 的 GIcon),将它们一一显示到前端界面。
** nemo_file_monitor_add 4346
** nemo_directory_async_state_changed 4418 ** start_or_stop_io 4342
** Message: starting file list in 0x2620ae0
** Message: load_directory called to monitor file list of 0x2620ae0
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** Message: starting file list in 0x2620900
** Message: load_directory called to monitor file list of 0x2620900
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** nemo_directory_force_reload_internal 2271
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
** nemo_directory_async_state_changed 4424 ** start_or_stop_io 4342
上面打印出来了一些函数调用顺序,起点函数就是 nemo_file_monitor_add.基本的 nemo I/O 梳理也就差不多了。
图标视图中 icon 的显示
这部分需要从 NemoView 和 NemoIconContainer 来介绍,在process_old_files 函数,遍历 files_added 链表,发出 add_file 信号,关联子类的 *_add_file 回调,比如 nemo_icon_view_add_file,最后调用 nemo_icon_container_add 将图标显示出来。
nemo_icon_container_add 的工作内容包括:新建 NemoIcon 对象,成员包括x、y坐标,预设值 UNPOSITIONED,scale、 NemoIconCanvasItem),新建的这个 icon 被加到 container->details->icons,准备好了之后,在闲时将图标添加到容器中显示出来。调用函数:
schedule_redo_layout redo_layout_callback redo_layout_internal finish_adding_new_icons
其中 finish_adding_new_icons 的主要工作如下:
for item in container->details->new_icons:
do
nemo_icon_container_update_icon
semi_position and no_position
finish_adding_icon
done
for item in semi_position_icons:
do
find_empty_location
icon_set_position
done
我对 semi_position_icon 的理解就是需要重新计算坐标位置的图标们,不知道对不对。判断依据:is_old_or_unknown_icon_data。 图标的 pixbuf 更新,文件名显示 editable_text, additional_text, embeded_text 等,都在函数 nemo_icon_container_update_icon 中完成。 要显示的图标从这里来:
icon_info = nemo_icon_container_get_icon_images (container, icon->data, icon_size,
&embedded_text,
icon == details->drop_target,
large_embedded_text, &embedded_text_needs_loading,
&has_open_window);
if(icon_info){
if (container->details->forced_icon_size > 0) {
pixbuf = nemo_icon_info_get_pixbuf_at_size (icon_info, icon_size);
} else {
pixbuf = nemo_icon_info_get_pixbuf (icon_info);
}
nemo_icon_info_get_attach_points (icon_info, &attach_points, &n_attach_points);
has_embedded_text_rect = nemo_icon_info_get_embedded_rect (icon_info,
&embedded_text_rect);
g_object_unref (icon_info);
}
图标视图中 icon 的销毁
当我们需要切换到其他目录时,当前的 nemo-icon-container 需要先清空,然后再加载新的目录,如果不清空,可能就导致上一次目录的文件图标和新目录的文件图标重叠了。 nemo-icon-container 在清空时,会遍历当前 container 中所有所有的 icons,挨个去释放这个 icon 的资源。 我们已经知道 NemoIcon 除了 icon 本身,还有一个 NemoIconCanvasItem 对象需要释放。这个 item 在新建的时候都会挂靠在一个 parent 下面,container->root.代码在 nemo_icon_container_add 中,
icon->item = NEMO_ICON_CANVAS_ITEM
(eel_canvas_item_new (EEL_CANVAS_GROUP (EEL_CANVAS (container)->root),
nemo_icon_canvas_item_get_type (),
"visible", FALSE,
NULL));
当切换目录的时候,需要销毁当前容器中的所有图标对象,于是item 在 dispose 阶段,每个 item 请求 canvas 画布重绘这个 item 所在的位置;执行 unmap、unrealize 等操作。最重要的是,在它的 parent 不为空时,执行 group_remove,将自己这个 item 从它的 parent 容器中删除。
这就会导致一个问题,我在分析 nemo 最新版 4.6.2 时(2020.7),发现如果打开一个巨大的目录,下有子文件十万个,当全部显示出来,我想切换到其他目录,需要等待40秒。于是在切换过程中,gdb 发现在切换时等待的时间内的堆栈如下:
group_remove
eel_canvas_item_destroy
icon_free
nemo_icon_container_clear
nemo_icon_view_clear_full
load_directory
nemo_view_load_location
load_directory 会执行 nemo_view_stop_loading 停止前一次的加载,并且发出 view 的clear 信号,从而开始 nemo_icon_view_clear 接着定位到时间最长的调用是在 nemo_icon_container_clear 中的
for (p = details->icons; p != NULL; p = p->next)
{
icon_free (p->data);
}
也就是说,因为 EelCanvasItem 在 destroy 时耗时过久,导致切换目录需要四十多秒。 在eel_canvas_item_dispose 函数中
if (item->parent)
group_remove (EEL_CANVAS_GROUP (item->parent), item);
问题就在于这个函数,在分析 group_remove 函数时,发现这部分逻辑的确有很大的优化空间。从 parent 中删除一个子对象,就是从一个链表中删除一个对象,也就意味着又是遍历链表。group_remove 会遍历这个 parent 的所有子节点,找到当前自己这个 item,把它从 parent 容器中移除。 这里就是双循环乘法,虽然内部循环在找到 item 就会 break,不至于N * N,但是 N * lgN 还是有的,其中 N 为 10w 数量级。 一次计算的微秒数可以忽略不计,但是 10w× lg10w 的时间还是很可观的,当我们从一个 10w 文件的目录切换到其他目录,就需要等待 40s,此时文件管理器完全没办法操作,CPU 处于疯狂计算状态。
不过,具体怎么优化,我还没想好,待补充。
异步I/O的坏处?
是否会让我们把图标视图中的图标重复画多次?比如我们在拿到文件信息之后立马把文件显示出来,但很可能一开始我们拿到的信息并不知道文件类型,而是把什么未知文件类型的图标显示出来了。然而在极短的时间后(ms级)我们拿到了关于这个文件的所有信息,包括文件图标,所以又画了一次。为了避免出现这样的问题,我们可以稍微加一点点延迟,等到拿到我们需要的基本信息之后再去显示文件图标等信息。从总的加载时间来看,这种异步操作还算比较快,但是在用户看起来并不快,因为他们不能立马看到数据。是否有更好的方法?
关于缓存
1、如果 NemoFile 缓存了错误的数据,我们需要能再去抓取新的信息,
我们可以用nemo_file_invalidate_attributes、nemo_file_invalidate_all_attributes 和nemo_directory_force_reload 这几个函数。加入我们在 Nemo 中写了一段代码知道肯定会影响到已经缓存的信息,就可以调用这仨函数中的某个来更新数据。
2、我们很难去决定一个文件信息要不要缓存,所以基于这么一个约定:如果NemoFile 的索引数清零信息就不会缓存。这也就意味着其他拥有我们 NemoFile 对象的程序决定了我们能不能拿到最新的文件信息。如果其他应用引用了我们的 NemoFile 对象,我们再去调用 nemo_file_call_when_ready 或者 nemo_file_monitor_add 只会拿到已经缓存过了的信息。
错误的缓存会引发很奇怪的问题,比如出现过samba 连接卸载后就无法再访问,就是因为右键菜单插件中获取了 nemo_file_info_get_location 但是用完了没有释放,导致下次再连接还是上一次断开的 url,无法再连接上。
问题描述:
用户连接 samba,进入samba子目录后,点击 location bar 回到上一级目录,再点击卸载 samba,之后就再也无法连接 samba,需要注销再登录。
问题原因:
文件压缩在实现文件管理器右键压缩功能插件时,没有释放内存导致的问题。
问题定位:
这个问题最开始定位是 nemo 或者 gvfs 的问题,特别是对于 samba、ftp 这类连接来说,nemo 这边请求文件信息,实际上是向 gvfs 请求。用户点击侧边栏书签或者在地址栏输入 smb 地址,最终走向都是 begin_location_change。在这个函数中,它会去 query file info,nemo 中调用的是抽象的 glib 接口,具体实现在 gvfs 中。正巧之前为了分析其他问题,我在 gvfs 中 gdaemonfile.c 的 query_info 接口添加了打印信息,每次打开 samba,进入子目录,回到上级目录等行为都会打印 query info 的信息,当出现问题的时候,就不能正常的去请求文件信息,卡住了。所以我先 gdb nemo 设置 break point 为 g_daemon_file_query_info 查看它的调用堆栈。
858 gdaemonfile.c: No such file or directory.
(gdb) bt
0 g_daemon_file_query_info (file=0x7fffdc091300, attributes=0x7ffff4e2cddb "standard::type", flags=G_FILE_QUERY_INFO_NONE,
cancellable=0x0, error=0x0) at gdaemonfile.c:858
1 0x00007ffff4d5dd91 in g_file_query_file_type () from /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
2 0x00000000004cd98c in get_is_dir_hack (file=) at nemo-action.c:1333
3 nemo_action_get_visibility (action=action@entry=0xfb00c0, selection=selection@entry=0xa124c0, parent=parent@entry=0xac7d50) at nemo-action.c:1431
4 0x00000000004aa878 in determine_visibility (data=, callback_data=) at nemo-view.c:6436
5 0x00007ffff47f2a9d in g_list_foreach () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
6 0x00000000004aca55 in update_actions_visibility (view=) at nemo-view.c:6455
7 real_update_menus (view=) at nemo-view.c:10439
8 0x000000000044cbce in nemo_icon_view_update_menus (view=0x11490f0) at nemo-icon-view.c:1609
9 0x00000000004a427f in nemo_view_update_menus (view=0x11490f0) at nemo-view.c:814
10 update_menus_timeout_callback (data=) at nemo-view.c:3907
11 0x00007ffff47f7113 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
12 0x00007ffff47f669a in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
13 0x00007ffff47f6a50 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
14 0x00007ffff47f6afc in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
15 0x00007ffff4db270d in g_application_run () from /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
16 0x0000000000431c79 in main (argc=2, argv=0x7fffffffe108) at nemo-main.c:136
根据这个调用堆栈,得到实际引发问题的函数是 real_update_menus,这个函数超级长,就不列出了。下一步就是需要找出是哪个函数导致无法请求正确的 file info,通过二分返回,定位到出问题的地方是:
reset_extension_actions_menu (view, selection);
get_all_extension_menu_items
nemo_menu_provider_get_file_items
实际出问题的就是这个 get_file_items, 找遍了 nemo 的代码,也没有发现实现的地方。后来看到 libnemo-extension 目录,才想起来 nemo 的插件机制,它只提供接口,实际的实现是插件去做的。我们系统中文件管理器的插件只有两个:文件共享和文件压缩。
通过卸载 nemo-share 和 nfs-file-compress,确定问题出在 nfs-file-compress。
[补充说明:get_file_items 的功能就是当你想开发一个插件,加到文件的右键菜单中,为了能操作你右键的那几个文件,需要实现文件管理器的接口来拿到文件信息,也就是这个 get_file_items。]
用户右键某个文件,想要压缩这个文件,就需要拿到文件的信息,名称、类型、大小等。分析文件压缩的代码,在它实现 >get_file_items 接口中,./nemo/nemo-filecompress.c:1149:
iface->get_file_items = nemo_fr_get_file_items;
在 nemo_fr_get_file_items 中看到的非常眼熟的打印信息
printf("...dir...name....%s\n",file_name_changed);
吐槽一下:这个 ...dir...name.... 简直无处不在,每次选中一个文件,都会打印这条,一直都想去掉这个打印信息,但是nemo 代码中却找不到,原来是在这里。
再仔细查看代码,
if(g_file_get_path(nemo_file_info_get_location(file))==NULL){...}
连续两个接口 g_file_get_path、nemo_file_info_get_location都是需要释放内存的地方,就这么扔在那里,还有
parent = nemo_file_info_get_parent_info (files->data);
parent = nemo_file_info_get_parent_info (file);
把这几个需要释放内存的对象分别做 free 、unref 之后,问题就解决了。之所以会出现再也连接不上samba 的原因是该释放的内存没有释放,下一个加载 location uri 时,还是尝试还是去加载已经断掉的那个地址对象,所以连不上,此问题充分说明了内存管理的重要性。