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

