mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix: fix memory leaks related to GradleBuildService and editor activity
This commit is contained in:
Binary file not shown.
@@ -253,8 +253,15 @@ abstract class BaseEditorActivity :
|
||||
flashError(string.msg_failed_list_files)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
checkIsDestroying()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
checkIsDestroying()
|
||||
preDestroy()
|
||||
super.onDestroy()
|
||||
postDestroy()
|
||||
@@ -424,6 +431,12 @@ abstract class BaseEditorActivity :
|
||||
viewModel.statusText = text
|
||||
viewModel.statusGravity = gravity
|
||||
}
|
||||
|
||||
private fun checkIsDestroying() {
|
||||
if (!isDestroying && isFinishing) {
|
||||
isDestroying = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUiDesignerResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data == null) {
|
||||
|
||||
+17
-8
@@ -137,14 +137,27 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
}
|
||||
|
||||
super.preDestroy()
|
||||
|
||||
|
||||
if (isDestroying) {
|
||||
|
||||
if (IDELanguageClientImpl.isInitialized()) {
|
||||
IDELanguageClientImpl.shutdown()
|
||||
}
|
||||
|
||||
try {
|
||||
stopLanguageServers()
|
||||
} catch (err: Exception) {
|
||||
log.error("Failed to stop editor services.")
|
||||
}
|
||||
|
||||
try {
|
||||
unbindService(buildServiceConnection)
|
||||
buildServiceConnection.onConnected = {}
|
||||
} catch (err: Throwable) {
|
||||
log.error("Unable to unbind service")
|
||||
} finally {
|
||||
(Lookup.DEFAULT.lookup(BuildService.KEY_BUILD_SERVICE) as? GradleBuildService?)?.setEventListener(null)
|
||||
(Lookup.DEFAULT.lookup(BuildService.KEY_BUILD_SERVICE) as? GradleBuildService?)
|
||||
?.setEventListener(null)
|
||||
Lookup.DEFAULT.unregister(BuildService.KEY_BUILD_SERVICE)
|
||||
viewModel.isBoundToBuildSerice = false
|
||||
}
|
||||
@@ -240,11 +253,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopServices() {
|
||||
override fun stopLanguageServers() {
|
||||
try {
|
||||
if (IDELanguageClientImpl.isInitialized()) {
|
||||
IDELanguageClientImpl.shutdown()
|
||||
}
|
||||
destroyLanguageServers(isChangingConfigurations)
|
||||
} catch (err: Throwable) {
|
||||
log.error("Unable to stop editor services. Please report this issue.", err)
|
||||
@@ -415,8 +425,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
|
||||
}
|
||||
|
||||
private fun closeProject(manualFinish: Boolean) {
|
||||
stopServices()
|
||||
|
||||
|
||||
// Make sure we close files
|
||||
// This will make sure that file contents are not erased.
|
||||
doCloseAll {
|
||||
|
||||
@@ -34,5 +34,5 @@ interface IProjectHandler {
|
||||
|
||||
fun startServices()
|
||||
fun initializeProject()
|
||||
fun stopServices()
|
||||
fun stopLanguageServers()
|
||||
}
|
||||
@@ -103,6 +103,9 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if (mInstance != null) {
|
||||
mInstance.activity = null;
|
||||
}
|
||||
mInstance = null;
|
||||
}
|
||||
|
||||
@@ -308,7 +311,7 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> map) {
|
||||
final var groups = new ArrayList<DiagnosticGroup>();
|
||||
var diagnosticMap = map;
|
||||
if (diagnosticMap == null || diagnosticMap.size() <= 0) return groups;
|
||||
if (diagnosticMap == null || diagnosticMap.size() == 0) return groups;
|
||||
|
||||
if (diagnosticMap.size() > 10) {
|
||||
LOG.warn("Limiting the diagnostics to 10 files");
|
||||
@@ -317,7 +320,7 @@ public class IDELanguageClientImpl implements ILanguageClient {
|
||||
|
||||
for (File file : diagnosticMap.keySet()) {
|
||||
var fileDiagnostics = diagnosticMap.get(file);
|
||||
if (fileDiagnostics == null || fileDiagnostics.size() <= 0) continue;
|
||||
if (fileDiagnostics == null || fileDiagnostics.size() == 0) continue;
|
||||
|
||||
// Trim the diagnostics list if we have too many diagnostic items.
|
||||
// Including a lot of diagnostic items will result in UI lag when they are shown
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -53,6 +52,7 @@ import com.itsaky.androidide.projects.builder.BuildService;
|
||||
import com.itsaky.androidide.resources.R;
|
||||
import com.itsaky.androidide.services.ToolingServerNotStartedException;
|
||||
import com.itsaky.androidide.shell.ProcessStreamsHolder;
|
||||
import com.itsaky.androidide.tooling.api.ForwardingToolingApiClient;
|
||||
import com.itsaky.androidide.tooling.api.IProject;
|
||||
import com.itsaky.androidide.tooling.api.IToolingApiClient;
|
||||
import com.itsaky.androidide.tooling.api.IToolingApiServer;
|
||||
@@ -66,7 +66,6 @@ import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.InputStreamLineReader;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -91,10 +90,18 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
private static final int NOTIFICATION_ID = R.string.app_name;
|
||||
private static final ILogger SERVER_System_err = newInstance("ToolingApiErrorStream");
|
||||
private final ILogger SERVER_LOGGER = newInstance("ToolingApiServer");
|
||||
private final IBinder mBinder = new GradleServiceBinder();
|
||||
private final IBinder mBinder = new GradleServiceBinder(this);
|
||||
private boolean isToolingServerStarted = false;
|
||||
private boolean isBuildInProgress = false;
|
||||
|
||||
/**
|
||||
* We do not provide direct access to GradleBuildService instance to the Tooling API launcher as
|
||||
* it may cause memory leaks. Instead, we create another client object which forwards all calls to us.
|
||||
* So, when the service is destroyed, we release the reference to the service from this client.
|
||||
*/
|
||||
private ForwardingToolingApiClient _toolingApiClient;
|
||||
private ToolingServerRunner toolingServerThread;
|
||||
private OutputReaderThread outputReader;
|
||||
private NotificationManager notificationManager;
|
||||
private IToolingApiServer server;
|
||||
@Nullable
|
||||
@@ -156,11 +163,32 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
if (server != null) {
|
||||
server.cancelCurrentBuild();
|
||||
server.shutdown();
|
||||
final var shutdown = server.shutdown();
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (shutdown != null) {
|
||||
try {
|
||||
LOG.info("Shutting down Tooling API server...");
|
||||
shutdown.get();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Failed to shutdown Tooling API server", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toolingServerThread != null) {
|
||||
toolingServerThread.interrupt();
|
||||
toolingServerThread = null;
|
||||
}
|
||||
|
||||
if (_toolingApiClient != null) {
|
||||
_toolingApiClient.setClient(null);
|
||||
_toolingApiClient = null;
|
||||
}
|
||||
|
||||
if (outputReader != null) {
|
||||
outputReader.interrupt();
|
||||
outputReader = null;
|
||||
}
|
||||
|
||||
isToolingServerStarted = false;
|
||||
@@ -191,7 +219,10 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
@NonNull
|
||||
@Override
|
||||
public IToolingApiClient getClient() {
|
||||
return this;
|
||||
if (_toolingApiClient == null) {
|
||||
_toolingApiClient = new ForwardingToolingApiClient(this);
|
||||
}
|
||||
return _toolingApiClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -487,19 +518,15 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
}
|
||||
|
||||
private void startServerOutputReader(InputStream err) {
|
||||
new Thread(new InputStreamLineReader(err, this::onServerOutput)).start();
|
||||
}
|
||||
|
||||
protected void onServerOutput(String line) {
|
||||
SERVER_System_err.error(line);
|
||||
}
|
||||
|
||||
public class GradleServiceBinder extends Binder {
|
||||
public GradleBuildService getService() {
|
||||
return GradleBuildService.this;
|
||||
if (outputReader == null) {
|
||||
outputReader = new OutputReaderThread(err, SERVER_System_err::error);
|
||||
}
|
||||
|
||||
if (!outputReader.isAlive()) {
|
||||
outputReader.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Handles events received from a Gradle build. */
|
||||
public interface EventListener {
|
||||
|
||||
|
||||
+2
-3
@@ -20,7 +20,6 @@ package com.itsaky.androidide.services.builder
|
||||
import android.content.ComponentName
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import com.itsaky.androidide.services.builder.GradleBuildService.GradleServiceBinder
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/**
|
||||
@@ -34,8 +33,8 @@ class GradleBuildServiceConnnection : ServiceConnection {
|
||||
private val log = ILogger.newInstance("GradleBuildServiceConnnection")
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val buildService = (service as GradleServiceBinder).service
|
||||
onConnected(buildService)
|
||||
val serviceBinder = service as GradleServiceBinder
|
||||
onConnected(serviceBinder.service!!)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.itsaky.androidide.services.builder
|
||||
|
||||
import android.os.Binder
|
||||
|
||||
/**
|
||||
* [Binder] implementation for [GradleBuildService] which provides a single-use instance of the
|
||||
* service.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
internal class GradleServiceBinder(service: GradleBuildService?) : Binder() {
|
||||
|
||||
private var released = false
|
||||
var service: GradleBuildService? = service
|
||||
private set
|
||||
get() {
|
||||
check(!released) { "GradleBuildService instance has been released" }
|
||||
return field.also { release() }
|
||||
}
|
||||
|
||||
private fun release() {
|
||||
service = null
|
||||
released = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.services.builder
|
||||
|
||||
import java.io.InputStream
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* Reads output from the Tooling API server.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class OutputReaderThread(private val input: InputStream, private var onRead: Consumer<String>?) :
|
||||
Thread("ToolingServerOutputReader") {
|
||||
|
||||
override fun run() {
|
||||
input.bufferedReader().use {
|
||||
var line: String? = it.readLine()
|
||||
while (line != null) {
|
||||
onRead?.accept(line)
|
||||
line = it.readLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun interrupt() {
|
||||
super.interrupt()
|
||||
onRead = null
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,7 @@ internal class ToolingServerRunner(
|
||||
|
||||
override fun interrupt() {
|
||||
super.interrupt()
|
||||
listener = null
|
||||
observer = null
|
||||
}
|
||||
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.tooling.api
|
||||
|
||||
import com.itsaky.androidide.models.LogLine
|
||||
import com.itsaky.androidide.tooling.api.messages.result.BuildResult
|
||||
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
* A [IToolingApiClient] which forwards all of its calls to the given client.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ForwardingToolingApiClient(var client: IToolingApiClient?) : IToolingApiClient {
|
||||
|
||||
override fun logMessage(line: LogLine) {
|
||||
client?.logMessage(line)
|
||||
}
|
||||
|
||||
override fun logOutput(line: String) {
|
||||
client?.logOutput(line)
|
||||
}
|
||||
|
||||
override fun prepareBuild() {
|
||||
client?.prepareBuild()
|
||||
}
|
||||
|
||||
override fun onBuildSuccessful(result: BuildResult) {
|
||||
client?.onBuildSuccessful(result)
|
||||
}
|
||||
|
||||
override fun onBuildFailed(result: BuildResult) {
|
||||
client?.onBuildFailed(result)
|
||||
}
|
||||
|
||||
override fun onProgressEvent(event: ProgressEvent) {
|
||||
client?.onProgressEvent(event)
|
||||
}
|
||||
|
||||
override fun getBuildArguments(): CompletableFuture<List<String>> {
|
||||
return client?.getBuildArguments() ?: CompletableFuture.completedFuture(emptyList())
|
||||
}
|
||||
|
||||
override fun checkGradleWrapperAvailability(): CompletableFuture<GradleWrapperCheckResult> {
|
||||
return client?.checkGradleWrapperAvailability()
|
||||
?: CompletableFuture.completedFuture(GradleWrapperCheckResult(false))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user