gui: Perform refresh of non-displayed classes in background thread

After renaming some classes needs to be redecompiled to reflect new state.
Move recompilation of non-displayed classes to background thread.
This should improve performance on weak machines.
This commit is contained in:
S-trace
2019-12-11 18:23:02 +03:00
committed by Soul Trace
parent 489fbb5e42
commit 797904afb5
5 changed files with 115 additions and 22 deletions
@@ -52,6 +52,13 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
System.gc();
LOG.debug("Memory usage: Before decompile: {}", UiUtils.memoryInfo());
runJob(cache.getDecompileJob());
LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo());
LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo());
long start = System.nanoTime();
runJob(cache.getRefreshJob());
cache.setRefreshJob(null);
LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000);
LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo());
runJob(cache.getIndexJob());
@@ -74,7 +81,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
}
private void runJob(BackgroundJob job) {
if (isCancelled()) {
if (isCancelled() || job == null) {
return;
}
progressPane.changeLabel(this, job.getInfoString());
@@ -0,0 +1,28 @@
package jadx.gui.jobs;
import java.util.Set;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
public class RefreshJob extends BackgroundJob {
private Set<JavaClass> refreshClasses;
public RefreshJob(JadxWrapper jadxWrapper, int threadsCount, Set<JavaClass> refreshClasses) {
super(jadxWrapper, threadsCount);
this.refreshClasses = refreshClasses;
}
protected void runJob() {
for (final JavaClass cls : refreshClasses) {
addTask(cls::refresh);
}
}
@Override
public String getInfoString() {
return "Refreshing: ";
}
}
@@ -396,12 +396,12 @@ public class MainWindow extends JFrame {
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
}
void resetIndex() {
public void resetIndex() {
int threadsCount = settings.getThreadsCount();
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
}
private synchronized void runBackgroundJobs() {
synchronized void runBackgroundJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
if (settings.isAutoStartJobs()) {
@@ -414,6 +414,12 @@ public class MainWindow extends JFrame {
}
}
synchronized void runBackgroundRefreshAndIndexJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
backgroundWorker.exec();
}
public synchronized void cancelBackgroundJobs() {
backgroundExecutor.cancelAll();
if (backgroundWorker != null) {
@@ -517,7 +523,7 @@ public class MainWindow extends JFrame {
treeModel.reload();
}
void reloadTree() {
public void reloadTree() {
treeReloading = true;
treeModel.reload();
@@ -12,6 +12,7 @@ import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.*;
@@ -27,6 +28,8 @@ import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.utils.files.InputFile;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.RefreshJob;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
@@ -35,6 +38,7 @@ import jadx.gui.treemodel.JPackage;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.codearea.CodePanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
@@ -152,6 +156,7 @@ public class RenameDialog extends JDialog {
}
private void rename() {
long start = System.nanoTime();
String renameText = renameField.getText();
if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) {
return;
@@ -166,7 +171,13 @@ public class RenameDialog extends JDialog {
dispose();
return;
}
refreshState(root);
long refreshStart = System.nanoTime();
int classes = refreshState(root);
long refreshTime = (System.nanoTime() - refreshStart) / 1000000;
long totalTime = (System.nanoTime() - start) / 1000000;
LOG.info("refreshState() took {} ms to update state, {} classes will be refreshed in background ({} ms total)", refreshTime,
classes,
totalTime);
dispose();
}
@@ -198,42 +209,73 @@ public class RenameDialog extends JDialog {
return true;
}
private void refreshState(RootNode rootNode) {
private int refreshState(RootNode rootNode) {
RenameVisitor renameVisitor = new RenameVisitor();
renameVisitor.init(rootNode);
mainWindow.getCacheObject().getNodeCache().refresh(node);
TabbedPane tabbedPane = mainWindow.getTabbedPane();
refreshTabs(tabbedPane);
refreshUsages();
mainWindow.resetIndex();
Set<JavaClass> updatedClasses = getUpdatedClasses();
mainWindow.reloadTree();
refreshTabs(mainWindow.getTabbedPane(), updatedClasses);
if (updatedClasses.size() > 0) {
setRefreshTask(updatedClasses);
}
return updatedClasses.size();
}
private void refreshTabs(TabbedPane tabbedPane) {
private void refreshTabs(TabbedPane tabbedPane, Set<JavaClass> updatedClasses) {
for (Map.Entry<JNode, ContentPanel> panel : tabbedPane.getOpenTabs().entrySet()) {
ContentPanel contentPanel = panel.getValue();
if (contentPanel instanceof ClassCodeContentPanel) {
JNode node = panel.getKey();
node.getRootClass().refresh(); // Update code cache
ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel;
CodePanel javaPanel = codePanel.getJavaCodePanel();
javaPanel.refresh();
tabbedPane.refresh(node);
JClass rootClass = node.getRootClass();
JavaClass javaClass = rootClass.getCls();
if (updatedClasses.contains(javaClass) || node.getRootClass().getCls() == javaClass) {
LOG.info("Refreshing rootClass " + javaClass.getRawName());
rootClass.refresh(); // Update code cache
ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel;
CodePanel javaPanel = codePanel.getJavaCodePanel();
javaPanel.refresh();
tabbedPane.refresh(node);
updatedClasses.remove(javaClass);
}
}
}
}
private void refreshUsages() {
private Set<JavaClass> getUpdatedClasses() {
Set<JavaClass> usageClasses = new HashSet<>();
CodeUsageInfo usageInfo = mainWindow.getCacheObject().getUsageInfo();
if (usageInfo == null) {
return;
if (usageInfo != null) {
usageInfo.getUsageList(node).forEach((node) -> {
JavaClass rootClass = node.getRootClass().getCls();
// LOG.info("updateUsages(): Going to update class {}", rootClass.getRealFullName());
usageClasses.add(rootClass);
});
// usageClasses.parallelStream().forEach(JavaClass::refresh);
}
return usageClasses;
}
HashSet<JavaClass> usageClasses = new HashSet<>();
usageInfo.getUsageList(node).forEach((node) -> usageClasses.add(node.getRootClass().getCls()));
usageClasses.forEach(JavaClass::refresh);
private void setRefreshTask(Set<JavaClass> refreshClasses) {
CacheObject cache = mainWindow.getCacheObject();
RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses);
LOG.info("Waiting for old refreshJob");
while (cache.getRefreshJob() != null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return;
}
}
LOG.info("Old refreshJob finished");
cache.setRefreshJob(refreshJob);
cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), mainWindow.getCacheObject(), mainWindow.getSettings().getThreadsCount()));
mainWindow.runBackgroundRefreshAndIndexJobs();
}
private void initCommon() {
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.RefreshJob;
import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.search.TextSearchIndex;
@@ -14,6 +15,7 @@ public class CacheObject {
private DecompileJob decompileJob;
private IndexJob indexJob;
private RefreshJob refreshJob;
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
@@ -89,4 +91,12 @@ public class CacheObject {
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
return lastSearchOptions;
}
public RefreshJob getRefreshJob() {
return refreshJob;
}
public void setRefreshJob(RefreshJob refreshJob) {
this.refreshJob = refreshJob;
}
}