資源不同步的問題對(duì)于插件開發(fā)的哥們應(yīng)該都不是很陌生,記得剛到兩年前剛接觸插件開發(fā)的時(shí)候,由于產(chǎn)品中代碼中很多都是用java.io.File操作文件資源,導(dǎo)致經(jīng)常有這種問題發(fā)生,例如刪除不掉、內(nèi)容更新失敗等。下班之前,以資源刪除失敗為例子,寫篇小隨筆,把這個(gè)資源不同步的問題多少說道說道。^_^
首先看個(gè)可能不陌生的錯(cuò)誤(錯(cuò)誤本質(zhì)上都是不同步引起,但是可能包裝形式很多):

下面是引起錯(cuò)誤的代碼:
1 public void run(IAction action) {
2 try {
3 //獲取一個(gè)存在的文件
4 IFile eclipseFile = ResourcesPlugin.getWorkspace().getRoot().getProject("project").getFile(new Path("folder/file.txt"));
5
6 //用java IO更新底層資源
7 eclipseFile.getLocation().toFile().setLastModified(eclipseFile.getLocalTimeStamp() + 100);
8
9 //刪除文件
10 eclipseFile.delete(false, null);
11 } catch (CoreException e) {
12 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, "aaa", 99, "刪除資源失敗", e));
13 }
14 }
【猜測(cè)】是不是我們用Java IO修改了文件,而Eclipse工作區(qū)不知道,這可能就是不同步?
【兩種狀態(tài):ResourceInfo VS IFileInfo】
一個(gè)文件資源的狀態(tài)描述,我們可以從兩個(gè)層面來看:一個(gè)Eclipse工作區(qū)層面的資源狀態(tài);二是文件系統(tǒng)層面的資源狀態(tài)。對(duì)于這兩者,Eclipse資源管理模塊中都有對(duì)應(yīng)的類型支持。
ResourceInfo :封裝了工作區(qū)對(duì)一個(gè)文件資源的描述,也就是我們常說的工作區(qū)資源樹上的一個(gè)數(shù)據(jù)節(jié)點(diǎn),Eclipse資源管理中的IResource系列接口本身也是ResourceInfo的代理,ResourceInfo主要操作如下:


ResourceInfo的主要常規(guī)獲取方式,Workspace.getResourceInfo:
1 public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) {
2 try {
3 if (path.segmentCount() == 0) {
4 ResourceInfo info = (ResourceInfo) tree.getTreeData();
5 Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$
6 return info;
7 }
8 ResourceInfo result = null;
9 if (!tree.includes(path))
10 return null;
11 if (mutable)
12 result = (ResourceInfo) tree.openElementData(path);
13 else
14 result = (ResourceInfo) tree.getElementData(path);
15 if (result != null && (!phantom && result.isSet(M_PHANTOM)))
16 return null;
17 return result;
18 } catch (IllegalArgumentException e) {
19 return null;
20 }
21 }
資源樹的影子出來了,我們獲取resource info的過程其實(shí)就是在資源樹上面定位對(duì)應(yīng)數(shù)據(jù)節(jié)點(diǎn)的過程。那可以自然的猜測(cè)(有興趣的Tx可以接著撒兩眼資源樹是如何實(shí)現(xiàn)的),ResourceInfo的獲取過程并不是每次都會(huì)產(chǎn)生一個(gè)新的ResourceInfo實(shí)例,因?yàn)橹庇X告訴我們這可能是性能敏感的。
IFileInfo:一個(gè)資源在特定時(shí)間點(diǎn)上的狀態(tài)快照(snapshot),可以理解為一個(gè)底層文件系統(tǒng)資源對(duì)應(yīng)的靜態(tài)只讀信息的集合。我們看一下它的獲取方式,IFileStore.fetchInfo()實(shí)現(xiàn)(有關(guān)IFleStore這里就省略了^_^):
1 public IFileInfo fetchInfo(int options, IProgressMonitor monitor) {
2 if (LocalFileNatives.usingNatives()) {
3 FileInfo info = LocalFileNatives.fetchFileInfo(filePath);
4 //natives don't set the file name on all platforms
5 if (info.getName().length() == 0)
6 info.setName(file.getName());
7 return info;
8 }
9 //in-lined non-native implementation
10 FileInfo info = new FileInfo(file.getName());
11 final long lastModified = file.lastModified();
12 if (lastModified <= 0) {
13 //if the file doesn't exist, all other attributes should be default values
14 info.setExists(false);
15 return info;
16 }
17 info.setLastModified(lastModified);
18 info.setExists(true);
19 info.setLength(file.length());
20 info.setDirectory(file.isDirectory());
21 info.setAttribute(EFS.ATTRIBUTE_READ_ONLY, file.exists() && !file.canWrite());
22 info.setAttribute(EFS.ATTRIBUTE_HIDDEN, file.isHidden());
23 return info;
24 }
可以看出來,每次獲取IFileInfo的過程是每次都產(chǎn)生新的實(shí)例,這個(gè)新的實(shí)例來描述該時(shí)間點(diǎn)上的文件狀態(tài)。

到這里我們知道了,ResourceInfo其實(shí)是內(nèi)存中Eclipse維護(hù)的一個(gè)東東,IFileInfo是實(shí)時(shí)獲取的,那么在特定的時(shí)間點(diǎn)上面,前者有可能和后者不統(tǒng)一。這是不是就是不同步問題的根源呢?
【資源不同步檢查】
為了驗(yàn)證上面的猜測(cè),我們追蹤調(diào)試一下文章開頭提到的測(cè)試代碼:

找到了檢查文件資源是否同步的代碼,FileSystemResourceManager.isSynchronized(IResource target, int depth),資源樹在檢查一個(gè)資源是否同步時(shí)候(IResourceTree.isisSynchronized(IResource target, int depth)),也是委托給了FileSystemResourceManager:
1 public boolean isSynchronized(IResource target, int depth) {
2 switch (target.getType()) {
3 case IResource.ROOT :
4 if (depth == IResource.DEPTH_ZERO)
5 return true;
6 //check sync on child projects.
7 depth = depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : depth;
8 IProject[] projects = ((IWorkspaceRoot) target).getProjects();
9 for (int i = 0; i < projects.length; i++) {
10 if (!isSynchronized(projects[i], depth))
11 return false;
12 }
13 return true;
14 case IResource.PROJECT :
15 if (!target.isAccessible())
16 return true;
17 break;
18 case IResource.FILE :
19 if (fastIsSynchronized((File) target))
20 return true;
21 break;
22 }
23 IsSynchronizedVisitor visitor = new IsSynchronizedVisitor(Policy.monitorFor(null));
24 UnifiedTree tree = new UnifiedTree(target);
25 try {
26 tree.accept(visitor, depth);
27 } catch (CoreException e) {
28 Policy.log(e);
29 return false;
30 } catch (IsSynchronizedVisitor.ResourceChangedException e) {
31 //visitor throws an exception if out of sync
32 return false;
33 }
34 return true;
35 }
我們接著看一下上面負(fù)責(zé)File級(jí)別同步檢查的FileSystemResourceManager.fastIsSynchronized方法:
1 public boolean fastIsSynchronized(File target) {
2 ResourceInfo info = target.getResourceInfo(false, false);
3 if (target.exists(target.getFlags(info), true)) {
4 IFileInfo fileInfo = getStore(target).fetchInfo();
5 if (!fileInfo.isDirectory() && info.getLocalSyncInfo() == fileInfo.getLastModified())
6 return true;
7 }
8 return false;
9 }
上面的紅線部分告訴我們:
對(duì)于一個(gè)文件級(jí)別的資源,判斷是否同步就是檢查Eclipse維護(hù)的時(shí)間戳和底層文件系統(tǒng)的時(shí)間戳是否一致?。?!
如果用Eclipse IResource API來修改文件資源,Eclipse自己會(huì)知道;如果用java IO或者java NIO來修改文件資源,Eclipse就一無所知,狀態(tài)維護(hù)就會(huì)出問題。不知道就出事情了,不同步只是后果之一,變化跟蹤也將失效,resource change event也將無從產(chǎn)生了^_^
【后記】
1、對(duì)于非File級(jí)別的資源,為什么同步檢查不是很嚴(yán)格呢?
因?yàn)樵诓煌僮飨到y(tǒng)不同類型分區(qū)上面,一個(gè)文件夾下面的文件資源被修改了,文件夾的時(shí)間戳并不保證會(huì)及時(shí)更新。這是很底層的東西了,就不接著講了。 如果你想修正這個(gè)問題,你可以對(duì)目錄時(shí)間戳做自己的維護(hù)(這是可行的,我們產(chǎn)品里面就是這么干的)。
2、ResourceInfo和IFileInfo好像不怎么使用?
確實(shí),Ecipse也不想讓開發(fā)者去直接使用它。例如:getResourceInfo是在Resource中提供的,而不是在IResource接口中定義的。
3、那如何同步呢?
IResource.refreshLocal(int depth, IProgressMonitor monitor)
本博客中的所有文章、隨筆除了標(biāo)題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請(qǐng)注明出處,謝謝!