Essa semana eu tive que implementar um serviço que fica rodando mesmo com o aplicativo android esteja fechado, foi muito trabalhoso achar o conteudo no forum e utilizando o GTP que muitas vezes responde errado e eu tive que ficar deduzindo com o conhecimento que eu tinha e estudos que realizei, mas como no fim tudo da certo, consegui criar e testar o serviço que será responsavel por enviar os dados do aparelho para o servidor.
links úteis e que foram utilizados:
www.b4x.com
www.b4x.com
Nesse exemplo que está funcionando estou utilizando PHP + MYSQL + B4A:
código para criar a TABELA no MYSQL:
código da API em PHP:
código do Manifest Editor:
código do Main
código do Starter
código do ServiceSync [CÓDIGO MAIS IMPORTANTE]
links úteis e que foram utilizados:
Android 14 / targetSdkVersion 34 and Services
B4A v13.0+ should be used with targetSdkVersion 34+. Android 14 continues the longtime trend of making services less flexible and more difficult to use (converging to iOS background tasks features from 2014). Many of the previous use cases for services are better covered by receivers...
android.jar / targetSdkVersion / minSdkVersion
There are several versioned components that affect the compilation process and the runtime behavior of our apps. The purpose of this tutorial is to explain the differences between them and help you choose which version to use. Each Android version is mapped to an api level. You can see this...
Nesse exemplo que está funcionando estou utilizando PHP + MYSQL + B4A:
código para criar a TABELA no MYSQL:
SQL:
CREATE TABLE teste_data (
id INT AUTO_INCREMENT PRIMARY KEY,
data DATETIME
);
código da API em PHP:
PHP:
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'nomeBanco');
define('DB_USER', 'usuarioBanco');
define('DB_PASS', 'senhaUsuarioBanco');
define('DB_PORT', '3306');
function getDb() {
static $conn = null;
if ($conn === null) {
$conn = new PDO(
'mysql:host='.DB_HOST.';port='.DB_PORT.';dbname='.DB_NAME.';charset=utf8',
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
return $conn;
}
$conn = getDb();
$data = $_GET['data'];
$sql = " INSERT INTO teste_data (data) VALUES (?)";
$stmt = $conn->prepare($sql);
$stmt->execute([$data]);
$lastId = $conn->lastInsertId();
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'id' => $lastId
]);
?>
código do Manifest Editor:
B4X:
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34"/>
<supports-screens android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.LightTheme)
'End of default text.
' Permite conexões HTTP não seguras (sem HTTPS) em Android 9+ (API 28+)
' Útil para testes ou se seu servidor não suporta HTTPS
' Porém, para publicação na Play Store, recomenda-se fortemente usar HTTPS e remover esta linha
CreateResourceFromFile(Macro, Core.NetworkClearText)
' Permissão geral para executar serviços em foreground (requerida para todos os apps com foreground service)
AddPermission(android.permission.FOREGROUND_SERVICE)
' Permissão específica para serviços do tipo dataSync (obrigatória no Android 14+ para foregroundServiceType="dataSync")
AddPermission(android.permission.FOREGROUND_SERVICE_DATA_SYNC)
' Define no AndroidManifest.xml o atributo android:foregroundServiceType="dataSync" na tag <service> do módulo ServiceSync
' Isso informa ao sistema que seu serviço em foreground é do tipo sincronização de dados
SetServiceAttribute(ServiceSync, android:foregroundServiceType, "dataSync")
código do Main
B4X:
#Region Project Attributes
#ApplicationLabel: servicoSincronizacaoDados
#VersionCode: 1
#VersionName:
'SupportedOrientations possible values: unspecified, landscape or portrait.
#SupportedOrientations: unspecified
#CanInstallToExternalStorage: False
#End Region
#Region Activity Attributes
#FullScreen: False
#IncludeTitle: True
#End Region
Sub Process_Globals
Private xui As XUI
End Sub
Sub Globals
Private CustomListView1 As CustomListView
Private Label1 As B4XView
End Sub
Sub Activity_Create(FirstTime As Boolean)
Activity.LoadLayout("Layout")
StartService(ServiceSync)
End Sub
Sub Activity_Resume
#if B4A
Wait For (CheckAndRequestNotificationPermission) Complete (HasPermission As Boolean)
If HasPermission = False Then
Log("no permission")
ToastMessageShow("no permission", True)
End If
#End If
carregarDadosNaoEnviados
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub
Sub Button1_Click
Dim data As String = Starter.obterData
Starter.vSQL.ExecNonQuery($"insert into teste_data (data) values ('${data}')"$)
Log("inserido: "& data)
ToastMessageShow("Data inserida com sucesso: " & data, False)
carregarDadosNaoEnviados
End Sub
Sub carregarDadosNaoEnviados
CustomListView1.Clear
Dim query As String = "SELECT * FROM teste_data WHERE sincronizado = 'N' ORDER BY data DESC"
Dim rs As ResultSet = Starter.vSQL.ExecQuery(query)
Dim total As Int = 0
Do While rs.NextRow
Dim id As String = rs.GetString("id")
Dim data As String = rs.GetString("data")
CustomListView1.AddTextItem(data, id)
total = total + 1
Loop
Label1.Text = $"Total pendente: ${total}"$
Log($"Total pendente: ${total}"$)
End Sub
#if B4A
Private Sub CheckAndRequestNotificationPermission As ResumableSub
Dim p As Phone
If p.SdkVersion < 33 Then Return True
Dim ctxt As JavaObject
ctxt.InitializeContext
Dim targetSdkVersion As Int = ctxt.RunMethodJO("getApplicationInfo", Null).GetField("targetSdkVersion")
If targetSdkVersion < 33 Then Return True
Dim NotificationsManager As JavaObject = ctxt.RunMethod("getSystemService", Array("notification"))
Dim NotificationsEnabled As Boolean = NotificationsManager.RunMethod("areNotificationsEnabled", Null)
If NotificationsEnabled Then Return True
Dim rp As RuntimePermissions
rp.CheckAndRequest(rp.PERMISSION_POST_NOTIFICATIONS)
Wait For Activity_PermissionResult (Permission As String, Result As Boolean) 'change to Activity_PermissionResult if non-B4XPages.
Log(Permission & ": " & Result)
Return Result
End Sub
#End If
código do Starter
B4X:
#Region Service Attributes
#StartAtBoot: False
#ExcludeFromLibrary: True
#End Region
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
Public xui As XUI
Public vSQL As SQL
End Sub
Sub Service_Create
'This is the program entry point.
'This is a good place to load resources that are not specific to a single activity.
iniciarBancoDados
End Sub
Sub Service_Start (StartingIntent As Intent)
Service.StopAutomaticForeground 'Starter service can start in the foreground state in some edge cases.
End Sub
Sub Service_TaskRemoved
'This event will be raised when the user removes the app from the recent apps list.
End Sub
'Return true to allow the OS default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
Return True
End Sub
Sub Service_Destroy
End Sub
Sub iniciarBancoDados
vSQL.Initialize(xui.DefaultFolder, "banco.db", True)
Try
vSQL.ExecNonQuery($"CREATE TABLE IF NOT EXISTS teste_data (
id integer primary key autoincrement,
data text,
sincronizado text default 'N',
data_sincronizado text default '');"$)
Catch
Log("Erro ao criar banco: " & LastException.Message)
End Try
End Sub
Sub obterData As String
Dim dia As String = NumberFormat(DateTime.GetDayOfMonth(DateTime.Now), 2, 0)
Dim mes As String = NumberFormat(DateTime.GetMonth(DateTime.Now), 2, 0)
Dim ano As String = DateTime.GetYear(DateTime.Now)
Dim hora As String = NumberFormat(DateTime.GetHour(DateTime.Now), 2, 0)
Dim minuto As String = NumberFormat(DateTime.GetMinute(DateTime.Now), 2, 0)
Dim segundo As String = NumberFormat(DateTime.GetSecond(DateTime.Now), 2, 0)
Return $"${ano}-${mes}-${dia} ${hora}:${minuto}:${segundo}"$
End Sub
código do ServiceSync [CÓDIGO MAIS IMPORTANTE]
B4X:
#Region Service Attributes
' Inicia o serviço automaticamente quando o dispositivo liga
#StartAtBoot: True
#End Region
Sub Process_Globals
Private nid As Int = 1 ' ID da notificação do serviço em foreground (único identificador)
End Sub
Sub Service_Create
' Aqui você pode colocar código que será executado apenas uma vez quando o serviço for criado
' No momento, está vazio porque nada é necessário na criação
End Sub
Sub Service_Start (StartingIntent As Intent)
' Essa rotina é chamada sempre que o serviço é iniciado ou reiniciado
' Desliga o modo automático de foreground para controlar manualmente no Android 12+
Service.AutomaticForegroundMode = Service.AUTOMATIC_FOREGROUND_NEVER
' Inicia o serviço em modo foreground (prioritário) mostrando uma notificação persistente
Service.StartForeground(nid, CreateNotification("Sincronizando..."))
' Aguarda a execução completa da sincronização de dados antes de continuar
Wait for (SincronizarDadosEFinalizarServico) Complete (naoUtilizado As Object)
' Agenda uma nova execução do serviço para daqui a 5 minutos
StartServiceAt(Me, DateTime.Now + 5 * DateTime.TicksPerMinute, True)
End Sub
Sub Service_Destroy
' Chamado quando o serviço é destruído
' Aqui poderia liberar recursos se tivesse algo em uso (não necessário neste caso)
End Sub
' Função principal que sincroniza dados e encerra o serviço ao terminar
Sub SincronizarDadosEFinalizarServico As ResumableSub
' Prepara a consulta SQL para buscar teste_data não sincronizadas ('N')
Dim query As String = "SELECT * FROM teste_data WHERE sincronizado = 'N'"
' Executa a consulta no banco de dados SQLite
Dim rs As ResultSet = Starter.vSQL.ExecQuery(query)
' Percorre todas as linhas retornadas pela consulta
Do While rs.NextRow
' Pega os dados da linha atual: id e data da coordenada
Dim id As String = rs.GetString("id")
Dim data As String = rs.GetString("data")
' Cria um novo objeto para fazer a requisição HTTP (enviar dados)
Dim job As HttpJob
job.Initialize("", Me)
' Envia os dados para o servidor via POST, passando "data" como parâmetro
job.Download2("https://seuservidor.com.br/api.php", Array As String("data", data))
' Aguarda a finalização do envio (resposta do servidor)
Wait For (job) JobDone (job As HttpJob)
' Se o envio foi bem sucedido
If job.Success Then
' Converte a resposta JSON em mapa para pegar valores facilmente
Dim response As Map = job.GetString.As(JSON).ToMap
' Log no console o id enviado para confirmar o sucesso
Log("enviado: " & response.GetDefault("id",""))
Dim dataSync As String = Starter.obterData
' Atualiza a tabela para marcar essa coordenada como sincronizada ('S')
Starter.vSQL.ExecNonQuery($"UPDATE teste_data SET sincronizado = 'S', data_sincronizado = '${dataSync}' WHERE id= '${id}'"$)
Else
' Se houve erro, log da mensagem para diagnóstico
Log("Erro ao sincronizar: " & job.ErrorMessage)
End If
' Libera os recursos usados pela requisição HTTP
job.Release
Loop
' Fecha o ResultSet para liberar recursos
rs.Close
' Remove a notificação da barra de status, pois o serviço vai terminar
Service.StopForeground(nid)
' Encerra o próprio serviço para liberar memória e recursos do sistema
StopService(Me)
' Finaliza a ResumableSub, retornando null (obrigatório em B4A)
Return Null
End Sub
' Cria e configura a notificação usada no serviço em foreground
Private Sub CreateNotification(Texto As String) As Notification
Dim n As Notification
' Inicializa a notificação com importância padrão (nível de visibilidade)
n.Initialize2(n.IMPORTANCE_DEFAULT)
' Define o ícone que aparecerá na notificação (deve estar no projeto)
n.Icon = "icon"
' A notificação não pode ser removida pelo usuário pois indica serviço ativo
n.AutoCancel = False
' Marca como evento contínuo (indica serviço rodando em background)
n.OnGoingEvent = True
' Define o título, texto e a ação ao clicar na notificação (abre Main)
n.SetInfo("Sincronização", Texto, Main)
' Retorna a notificação pronta para uso
Return n
End Sub
Attachments
Last edited: