You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi Android服务重启后TIdHttp执行Get触发Segmentation fault(11)

排查思路与解决方案

这个问题我之前在Delphi Android服务开发中遇到过类似场景,核心是服务停止后未正确清理后台线程与Indy全局资源,导致重启服务时内存访问异常。结合你的代码和错误信息,咱们一步步拆解排查方向:


1. 错误根源定位:TThreadList.LockList的内存损坏

错误发生在System.Generics.Collections的TThreadList.LockList中,说明Indy组件内部维护的线程安全列表(比如管理SSL连接、请求队列的全局列表)的锁对象已经失效或内存损坏。这种情况通常是因为:

  • 服务停止时,后台线程仍在运行,Indy的全局资源被异常修改
  • 进程级的Indy全局状态(如OpenSSL上下文)在服务销毁时未正确保留,重启服务时无法正常初始化

2. 具体排查与修复步骤

(1)确保服务停止时,所有后台线程已完全退出

你的代码中,服务收到StopIntent后直接调用JavaService.stopSelf,但此时后台线程可能还在执行TIdHttp.Get请求,线程未终止就销毁服务,会导致Indy内部资源处于不一致状态。

修改方案:在停止服务前,主动终止并等待所有后台线程完成:

// 在TDM中添加线程管理列表
private
  FThreadList: TThreadList<TThread>;

// TDM创建时初始化列表
procedure TDM.DataModuleCreate(Sender: TObject);
begin
  FThreadList := TThreadList<TThread>.Create;
end;

// TDM销毁时清理线程
procedure TDM.DataModuleDestroy(Sender: TObject);
var
  Threads: TList<TThread>;
  I: Integer;
begin
  Threads := FThreadList.LockList;
  try
    for I := 0 to Threads.Count - 1 do
    begin
      Threads[I].Terminate;
      Threads[I].WaitFor;
    end;
  finally
    FThreadList.UnlockList;
  end;
  FThreadList.Free;
end;

// 修改StopIntent处理逻辑
function TDM.AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
var
  Threads: TList<TThread>;
  I: Integer;
begin
  if Assigned(Intent) then
  begin
    if Intent.getAction.equalsIgnoreCase(StringToJString('StopIntent')) then
    begin
      // 第一步:终止所有后台线程
      Threads := FThreadList.LockList;
      try
        for I := 0 to Threads.Count - 1 do
          Threads[I].Terminate;
      finally
        FThreadList.UnlockList;
      end;

      // 第二步:等待所有线程完成退出
      while FThreadList.LockList.Count > 0 do
      begin
        FThreadList.UnlockList;
        Sleep(100);
        TThread.Yield;
      end;
      FThreadList.UnlockList;

      Result := TJService.JavaClass.START_NOT_STICKY;
      JavaService.stopSelf;
    end
    else if Intent.getAction.equalsIgnoreCase(StringToJString('StartIntent')) then
    begin
      Result := TJService.JavaClass.START_STICKY;
      start_thread;
    end;
  end;
end;

// 修改start_thread,将线程加入管理列表
procedure TDM.start_thread;
var
  LThread: TThread;
begin
  LThread := TThread.CreateAnonymousThread(
    procedure
    var 
      pagina: string; 
      pegar: tidhttp; 
      seguro: TIdSSLIOHandlerSocketOpenSSL; 
      compressor: TIdCompressorZLib;
    begin
      try
        pegar := tidhttp.Create(nil);
        compressor := TIdCompressorZLib.Create(nil);
        seguro := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
        try
          // 线程终止检查,避免无效操作
          if TThread.CurrentThread.Terminated then Exit;

          pegar.Request.useragent := 'Mozilla/5.0 (Linux; Android 7.0; PLUS Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Mobile Safari/537.36';
          pegar.ReadTimeout := 60000;
          pegar.ConnectTimeout := 60000;
          pegar.HTTPOptions := [hoForceEncodeParams];
          pegar.IOHandler := seguro;
          pegar.compressor := compressor;
          pegar.HandleRedirects := false;
          pegar.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
          pegar.Request.AcceptLanguage := 'pt-BR,en-US;q=0.8,pt;q=0.5,en;q=0.3';
          pegar.Request.ContentType := 'application/json';
          pegar.Request.Connection := 'keep-alive';

          try
            if not TThread.CurrentThread.Terminated then
              pagina := pegar.Get('https://myserver.com/api/versions/count');
          except
            enviar_log('Connection error');
          end;
        finally
          compressor.Free;
          seguro.Free;
          pegar.Free;
        end;
      finally
        // 线程结束后从管理列表移除
        FThreadList.Remove(TThread.CurrentThread);
        enviar_log('Exit');
      end;
    end);
  LThread.FreeOnTerminate := False; // 改为手动管理,确保能等待线程完成
  FThreadList.Add(LThread);
  LThread.Start;
end;

(2)确保OpenSSL库进程级加载,避免重复加载/卸载

TIdSSLIOHandlerSocketOpenSSL依赖OpenSSL动态库,在Android上如果服务停止时误卸载了库(或未正确初始化),重启服务时会导致内存异常。建议在宿主APP启动时一次性加载OpenSSL,而非每次服务启动才加载:

uses IdSSLOpenSSLHeaders;

procedure Tfrm_principal.FormCreate(Sender: TObject);
begin
  if not LoadOpenSSL then
  begin
    enviar_log('OpenSSL库加载失败');
    // 处理加载失败逻辑,比如提示用户
  end;
end;

(3)升级Indy组件版本

部分旧版本的Indy 10在Android服务场景下存在线程安全bug,建议升级到最新稳定版(如Indy 10.6.2.5494及以上),官方已修复不少Android平台的资源泄漏与线程冲突问题。


3. 核心总结

这个问题本质是Android服务生命周期与后台线程的资源管理冲突:

  • 服务停止时未等待线程完成,导致Indy内部全局资源(如线程安全列表)损坏
  • 进程级的OpenSSL库未正确保留,重启服务时初始化失败

通过上述线程管理和全局资源初始化的调整,基本可以解决重启服务时的Segmentation fault问题。

内容的提问来源于stack exchange,提问作者Alexandre R. Pires

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:23:08