gpt4 book ai didi

Android - 如何在长生命周期后台线程中运行套接字

转载 作者:塔克拉玛干 更新时间:2023-11-02 18:57:59 24 4
gpt4 key购买 nike

我正在构建一个连接 android 客户端和 java 服务器(在我的电脑上运行)之间的简单聊天。用户可以向/从 Android 应用程序和桌面服务器发送和接收消息。
我现在正在处理如何在不同于 UI 线程 的线程中运行客户端套接字的问题。

我看到了使用 AsyncTask 的解决方案,但由于用户可能会在 长连续时间 内使用该应用程序进行通信,因此 AsyncTask 看起来是一种糟糕的方法。

AsyncTasks should ideally be used for short operations (a few seconds at the most.) API

因为我需要客户端套接字始终监听来自桌面服务器的消息,所以我想创建新的Thread 接收Runnable 实现类。

我的问题
1. 在哪个“线程机制”中放置客户端套接字行(ThreadIntentService)?

Socket client = new Socket(host, port);
InputStreamReader inputStreamReader = new InputStreamReader(client.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
while ((messageFromServer = bufferedReader.readLine()) != null) { //... }

2。客户端套接字(从与 主线程 不同的线程运行)如何将 messageFromServer 发送到 TextView

  1. 当用户输入文本并单击按钮时,我如何将用户消息从应用程序发送到服务器(当然使用客户端套接字)?

谢谢!

最佳答案

我创建了一个类似的应用程序,并使用了在后台运行的服务。

  1. 我已经从 IntentService 类中复制了代码并更新了 handleMessage(Message msg) 方法并删除了 stopSelf(msg.arg1); 行。这样你就有了一个在后台运行的服务。之后,我使用线程进行连接。
  2. 这里有两个选择。将数据存储到数据库中,GUI 会自行刷新。或者使用 LocalBroadcastManager .
  3. 在这里您还可以将数据存储到数据库中或以特殊 Intent 启动服务。

这是我的实现。我希望你能理解代码。

public class KeepAliveService extends Service {

/**
* The source of the log message.
*/
private static final String TAG = "KeepAliveService";

private static final long INTERVAL_KEEP_ALIVE = 1000 * 60 * 4;

private static final long INTERVAL_INITIAL_RETRY = 1000 * 10;

private static final long INTERVAL_MAXIMUM_RETRY = 1000 * 60 * 2;

private ConnectivityManager mConnMan;

protected NotificationManager mNotifMan;

protected AlarmManager mAlarmManager;

private boolean mStarted;

private boolean mLoggedIn;

protected static ConnectionThread mConnection;

protected static SharedPreferences mPrefs;

private final int maxSize = 212000;

private Handler mHandler;

private volatile Looper mServiceLooper;

private volatile ServiceHandler mServiceHandler;

private final class ServiceHandler extends Handler {
public ServiceHandler(final Looper looper) {
super(looper);
}

@Override
public void handleMessage(final Message msg) {
onHandleIntent((Intent) msg.obj);
}
}

public static void actionStart(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_START)));
}

public static void actionStop(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_STOP)));
}

public static void actionPing(final Context context) {
context.startService(SystemHelper.createExplicitFromImplicitIntent(context, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER)));
}

@Override
public void onCreate() {
Log.i(TAG, "onCreate called.");
super.onCreate();

mPrefs = getSharedPreferences("KeepAliveService", MODE_PRIVATE);

mConnMan = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

mNotifMan = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

mHandler = new Handler();

final HandlerThread thread = new HandlerThread("IntentService[KeepAliveService]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);

// If our process was reaped by the system for any reason we need to
// restore our state with merely a
// call to onCreate.
// We record the last "started" value and restore it here if necessary.
handleCrashedService();
}

@Override
public void onDestroy() {
Log.i(TAG, "Service destroyed (started=" + mStarted + ")");
if (mStarted) {
stop();
}
mServiceLooper.quit();
}

private void handleCrashedService() {
Log.i(TAG, "handleCrashedService called.");
if (isStarted()) {
// We probably didn't get a chance to clean up gracefully, so do it now.
stopKeepAlives();

// Formally start and attempt connection.
start();
}
}

/**
* Returns the last known value saved in the database.
*/
private boolean isStarted() {
return mStarted;
}

private void setStarted(final boolean started) {
Log.i(TAG, "setStarted called with value: " + started);
mStarted = started;
}

protected void setLoggedIn(final boolean value) {
Log.i(TAG, "setLoggedIn called with value: " + value);
mLoggedIn = value;
}

protected boolean isLoggedIn() {
return mLoggedIn;
}

public static boolean isConnected() {
return mConnection != null;
}

@Override
public void onStart(final Intent intent, final int startId) {
final Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
Log.i(TAG, "Service started with intent : " + intent);

onStart(intent, startId);

return START_NOT_STICKY;
}

private void onHandleIntent(final Intent intent) {
if (IntentActions.KEEP_ALIVE_SERVICE_STOP.equals(intent.getAction())) {
stop();

stopSelf();
} else if (IntentActions.KEEP_ALIVE_SERVICE_START.equals(intent.getAction())) {
start();
} else if (IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER.equals(intent.getAction())) {
keepAlive(false);
}
}

@Override
public IBinder onBind(final Intent intent) {
return null;
}

private synchronized void start() {
if (mStarted) {
Log.w(TAG, "Attempt to start connection that is already active");
setStarted(true);
return;
}

try {
registerReceiver(mConnectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
} catch (final Exception e) {
Log.e(TAG, "Exception occurred while trying to register the receiver.", e);
}

if (mConnection == null) {
Log.i(TAG, "Connecting...");
mConnection = new ConnectionThread(Config.PLUGIN_BASE_HOST, Config.PLUGIN_BASE_PORT);
mConnection.start();
}
}

private synchronized void stop() {
if (mConnection != null) {
mConnection.abort(true);
mConnection = null;
}

setStarted(false);

try {
unregisterReceiver(mConnectivityChanged);
} catch (final Exception e) {
Log.e(TAG, "Exception occurred while trying to unregister the receiver.", e);
}
cancelReconnect();
}

/**
* Sends the keep-alive message if the service is started and we have a
* connection with it.
*/
private synchronized void keepAlive(final Boolean forced) {
try {
if (mStarted && isConnected() && isLoggedIn()) {
mConnection.sendKeepAlive(forced);
}
} catch (final IOException e) {
Log.w(TAG, "Error occurred while sending the keep alive message.", e);
} catch (final JSONException e) {
Log.w(TAG, "JSON error occurred while sending the keep alive message.", e);
}
}


/**
* Uses the {@link android.app.AlarmManager} to start the keep alive service in every {@value #INTERVAL_KEEP_ALIVE} milliseconds.
*/
private void startKeepAlives() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INTERVAL_KEEP_ALIVE, INTERVAL_KEEP_ALIVE, pi);
}

/**
* Removes the repeating alarm which was started by the {@link #startKeepAlives()} function.
*/
private void stopKeepAlives() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_PING_SERVER), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.cancel(pi);
}

public void scheduleReconnect(final long startTime) {
long interval = mPrefs.getLong("retryInterval", INTERVAL_INITIAL_RETRY);

final long now = System.currentTimeMillis();
final long elapsed = now - startTime;

if (elapsed < interval) {
interval = Math.min(interval * 4, INTERVAL_MAXIMUM_RETRY);
} else {
interval = INTERVAL_INITIAL_RETRY;
}

Log.i(TAG, "Rescheduling connection in " + interval + "ms.");

mPrefs.edit().putLong("retryInterval", interval).apply();

final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_RECONNECT), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, now + interval, pi);
}

public void cancelReconnect() {
final PendingIntent pi = PendingIntent.getService(this, 0, new Intent(IntentActions.KEEP_ALIVE_SERVICE_RECONNECT), PendingIntent.FLAG_UPDATE_CURRENT);
mAlarmManager.cancel(pi);
}

private synchronized void reconnectIfNecessary() {
if (mStarted && !isConnected()) {
Log.i(TAG, "Reconnecting...");

mConnection = new ConnectionThread(Config.PLUGIN_BASE_HOST, Config.PLUGIN_BASE_PORT);
mConnection.start();
}
}

private final BroadcastReceiver mConnectivityChanged = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final NetworkInfo info = mConnMan.getActiveNetworkInfo(); // (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);

final boolean hasConnectivity = info != null && info.isConnected();

Log.i(TAG, "Connecting changed: connected=" + hasConnectivity);

if (hasConnectivity) {
reconnectIfNecessary();
} else if (mConnection != null) {
mConnection.abort(false);
mConnection = null;
}
}
};

protected class ConnectionThread extends Thread {
private final Socket mSocket;

private final String mHost;

private final int mPort;

private volatile boolean mAbort = false;

public ConnectionThread(final String host, final int port) {
mHost = host;
mPort = port;
mSocket = new Socket();
}

/**
* Returns whether we have an active internet connection or not.
*
* @return <code>true</code> if there is an active internet connection.
* <code>false</code> otherwise.
*/
private boolean isNetworkAvailable() {
final NetworkInfo info = mConnMan.getActiveNetworkInfo();
return info != null && info.isConnected();
}

@Override
public void run() {
final Socket s = mSocket;

final long startTime = System.currentTimeMillis();

try {
// Now we can say that the service is started.
setStarted(true);

// Connect to server.
s.connect(new InetSocketAddress(mHost, mPort), 20000);

Log.i(TAG, "Connection established to " + s.getInetAddress() + ":" + mPort);

// Start keep alive alarm.
startKeepAlives();

final DataOutputStream dos = new DataOutputStream(s.getOutputStream());
final BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));

// Send the login data.
final JSONObject login = new JSONObject();

// Send the login message.
dos.write((login.toString() + "\r\n").getBytes());

// Wait until we receive something from the server.
String receivedMessage;
while ((receivedMessage = in.readLine()) != null) {
Log.i(TAG, "Received data: " + receivedMessage);
processMessagesFromServer(dos, receivedMessage);
}

if (!mAbort) {
Log.i(TAG, "Server closed connection unexpectedly.");
}
} catch (final IOException e) {
Log.e(TAG, "Unexpected I/O error.", e);
} catch (final Exception e) {
Log.e(TAG, "Exception occurred.", e);
} finally {
setLoggedIn(false);
stopKeepAlives();

if (mAbort) {
Log.i(TAG, "Connection aborted, shutting down.");
} else {
try {
s.close();
} catch (final IOException e) {
// Do nothing.
}

synchronized (KeepAliveService.this) {
mConnection = null;
}

if (isNetworkAvailable()) {
scheduleReconnect(startTime);
}
}
}
}

/**
* Sends the PING word to the server.
*
* @throws java.io.IOException if an error occurs while writing to this stream.
* @throws org.json.JSONException
*/
public void sendKeepAlive(final Boolean forced) throws IOException, JSONException {
final JSONObject ping = new JSONObject();

final Socket s = mSocket;
s.getOutputStream().write((ping.toString() + "\r\n").getBytes());
}

/**
* Aborts the connection with the server.
*/
public void abort(boolean manual) {
mAbort = manual;

try {
// Close the output stream.
mSocket.shutdownOutput();
} catch (final IOException e) {
// Do nothing.
}

try {
// Close the input stream.
mSocket.shutdownInput();
} catch (final IOException e) {
// Do nothing.
}

try {
// Close the socket.
mSocket.close();
} catch (final IOException e) {
// Do nothing.
}

while (true) {
try {
join();
break;
} catch (final InterruptedException e) {
// Do nothing.
}
}
}
}

public void processMessagesFromServer(final DataOutputStream dos, final String receivedMessage) throws IOException {
}

}

您可以通过调用KeepAliveService.actionStart()来启动服务,您也可以定义自定义函数。

请注意,只有当您调用 KeepAliveService.actionStop() 时,该服务才会停止。否则它将永远运行。如果你打电话KeepAliveService.actionSendMessage(String message) 然后 intent 将传递给服务,您可以轻松处理它。

编辑:

SystemHelper 类只是一个包含静态方法的实用类。

public class SystemHelper {

/**
* Android Lollipop, API 21 introduced a new problem when trying to invoke implicit intent,
* "java.lang.IllegalArgumentException: Service Intent must be explicit"
*
* If you are using an implicit intent, and know only 1 target would answer this intent,
* This method will help you turn the implicit intent into the explicit form.
*
* Inspired from SO answer: http://stackoverflow.com/a/26318757/1446466
* @param context the application context
* @param implicitIntent - The original implicit intent
* @return Explicit Intent created from the implicit original intent
*/
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
Log.i(TAG, "createExplicitFromImplicitIntent ... called with intent: " + implicitIntent);
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
Log.i(TAG, "createExplicitFromImplicitIntent ... resolveInfo is null or there are more than one element.");
return null;
}

// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);

Log.i(TAG, "createExplicitFromImplicitIntent ... found package name:" + packageName + ", class name: " + className + ".");

// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);

// Set the component to be explicit
explicitIntent.setComponent(component);

return explicitIntent;
}
}

Config 类。

public class Config {

public static final String PACKAGE_NAME = "com.yourapp.package";
public static final String PLUGIN_BASE_HOST = "test.yoursite.com";
public static final int PLUGIN_BASE_PORT = 10000;
}

还有 IntentActions 类。

public class IntentActions {

public static final String KEEP_ALIVE_SERVICE_START = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_START";
public static final String KEEP_ALIVE_SERVICE_STOP = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_STOP";
public static final String KEEP_ALIVE_SERVICE_PING_SERVER = Config.PACKAGE_NAME + ".intent.action.KEEP_ALIVE_SERVICE_PING_SERVER";
}

在 AndroidManifest 文件中,服务定义如下:

<service android:name="com.yourapp.package.services.KeepAliveService"
android:exported="false">
<intent-filter>
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_START" />
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_STOP" />
<action android:name="com.yourapp.package.intent.action.KEEP_ALIVE_SERVICE_PING_SERVER" />
</intent-filter>
</service>

关于Android - 如何在长生命周期后台线程中运行套接字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32247141/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com