我有一个智能手机必须通过SSLSocket连接到本地服务器的应用程序。我在5款不同的智能手机上测试了我的应用程序:Moto G2(6.0),Redmi 3S(6.0.1),LG K5(6.0),Moto G5 Plus(7.1.1)和OnePlus 5(8.0)。 Moto G5 Plus是唯一一个能够解决这个问题的人。
这是导致问题行为的行。所有测试都在同一网络上完成。
socket = (SSLSocket) sslContext.getSocketFactory().createSocket(serverAddress, serverPort);
对于这种行为,Moto G5 Plus或Android 7+是否存在任何已知问题?
编辑:更多测试导致Android系统在确定已连接WiFi接口但没有互联网时试图强制Socket通过移动网络连接的想法。 有没有办法强制Socket使用WiFi而不是移动网络?
答案 0 :(得分:3)
免责声明:我没有对此进行测试,所以我真的不确定它是否有效。
Network
类有一个bind(Socket)
方法,也许你可以找到wifi网络,然后将它绑定到你的套接字。从文档来看,这似乎是你需要的,它说:
/**
* Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
* will be sent on this {@code Network}, irrespective of any process-wide network binding set by
* {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
*/
Socket
在绑定到网络之前不应该连接,所以我认为您应该使用socketFactory.createSocket()
创建它,并在绑定后才连接它。
所以,你应该先找到你的Network
(Kotlin):
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val wifiNetwork = connectivityManager.allNetworks.firstOrNull {
val info = connectivityManager.getNetworkInfo(it)
info.type == ConnectivityManager.TYPE_WIFI
}
或(Java)
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
Network wifiNetwork = null;
for(Network network : connectivityManager.getAllNetworks()){
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI){
wifiNetwork = network;
break;
}
}
然后将其绑定到Socket
并最终连接(Kotlin):
wifiNetwork?.bindSocket(socket)
val socketAddress = InetSocketAddress(hostname, port)
socket.connect(socketAddress)
或(Java)
if(wifiNetwork != null){
wifiNetwork.bindSocket(socket);
}
InetSocketAddress socketAddress = InetSocketAddress(hostName, port);
socket.connect(socketAddress);
注意,它需要ACCESS_NETWORK_STATE
权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
答案 1 :(得分:2)
我希望它可以帮到你,我刚刚在github上找到了你的解决方案。 有关详细信息和官方链接,请check this out,我认为这对您有所帮助。 如果不是答案,请忽略这个答案。
我们正在使用AsyncTask来避免网络访问的StrictMode致命错误(查看引用)。 StrictMode策略只是禁止我们影响UI线程。
/* AsyncTask class which manages connection with server app and is sending shutdown command.
*/
public class ShutdownAsyncTask extends AsyncTask<String, String, TCPClient> {
private static final String COMMAND = "shutdown -s" ;
private TCPClient tcpClient ;
private Handler mHandler ;
private static final String TAG = "ShutdownAsyncTask";
/**
* ShutdownAsyncTask constructor with handler passed as argument. The UI is updated via handler.
* In doInBackground(...) method, the handler is passed to TCPClient object.
* @param mHandler Handler object that is retrieved from MainActivity class and passed to TCPClient
* class for sending messages and updating UI.
*/
public ShutdownAsyncTask(Handler mHandler){
this.mHandler = mHandler;
}
/**
* Overriden method from AsyncTask class. There the TCPClient object is created.
* @param params From MainActivity class empty string is passed.
* @return TCPClient object for closing it in onPostExecute method.
*/
@Override
protected TCPClient doInBackground(String... params) {
Log.d(TAG, "In do in background");
try{
tcpClient = new TCPClient(mHandler,
COMMAND,
"192.168.1.1",
new TCPClient.MessageCallback() {
@Override
public void callbackMessageReceiver(String message) {
publishProgress(message);
}
});
}catch (NullPointerException e){
Log.d(TAG, "Caught null pointer exception");
e.printStackTrace();
}
tcpClient.run();
return null;
}
在这个AsyncTask中,我们正在创建TCPClient对象(如下所述)。在TCPClient构造函数中,我们传递Handler对象以更改UI,COMMAND - 带有&#34; shutdown -s&#34的字符串;关闭计算机的命令,IP号码 - 服务器IP号码;回调对象 - 当我们获得服务器响应时,回调方法&#39; messageCallbackReceiver&#39;正在开始发布进度&#39;方法,即将进度发布到onProgressUpdate&#39; AsyncTask的方法。
/**
* Overriden method from AsyncTask class. Here we're checking if server answered properly.
* @param values If "restart" message came, the client is stopped and computer should be restarted.
* Otherwise "wrong" message is sent and 'Error' message is shown in UI.
*/
@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
Log.d(TAG, "In progress update, values: " + values.toString());
if(values[0].equals("shutdown")){
tcpClient.sendMessage(COMMAND);
tcpClient.stopClient();
mHandler.sendEmptyMessageDelayed(MainActivity.SHUTDOWN, 2000);
}else{
tcpClient.sendMessage("wrong");
mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
tcpClient.stopClient();
}
}
收到正确的消息后,我们正在发送命令,或者如果收到错误的消息,我们正在发送消息&#34;错误&#34;并停止客户。在此之后,我们将被转移到'onPostExecute&#39;方法:
@Override
protected void onPostExecute(TCPClient result){
super.onPostExecute(result);
Log.d(TAG, "In on post execute");
if(result != null && result.isRunning()){
result.stopClient();
}
mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 4000);
}
}
一步一步:
- &gt; AsyncTask正在创建TCPClient对象。
- &gt;在TCPClient构造函数中,我们传递Handler,Command,IP Number和Callback对象。
- &gt;当TCPClient开始连接时,它会发送消息&#34; shutdown&#34;到服务器。
- &gt;当我们从服务器接收消息时,回调会将其传递给&#39; onProgressUpdate&#39;。
- &gt;如果收到的消息(来自服务器的响应)等于&#34; shutdown&#34;,我们正在向服务器发送命令。
- &gt;发送后我们停止客户端,将客户转移到&#39; onPostExecute&#39;方法
- &gt;同时,处理程序正在接收带有“消息”的空消息。 MainActivity中定义的整数,负责更新GUI。
小部件UI的更新方式示例:
mHandler = new Handler(){
public void handleMessage(Message msg) {
switch(msg.what){
case SHUTDOWN:
Log.d(mTag, "In Handler's shutdown");
views = new RemoteViews(context.getPackageName(), R.layout.activity_main);
widget = new ComponentName(context, MainActivity.class);
awManager = AppWidgetManager.getInstance(context);
views.setTextViewText(R.id.state, "Shutting PC...");
awManager.updateAppWidget(widget,views);
break;
此类负责维护连接。我会一步一步解释:
在第一步中,我们可以看到从ShutdownAsyncTask和其他人传递的对象。另外,我们可以看到sendMessage和stopClient方法。
public class TCPClient {
private static final String TAG = "TCPClient" ;
private final Handler mHandler ;
private String ipNumber, incomingMessage, command;
BufferedReader in ;
PrintWriter out ;
private MessageCallback listener = null ;
private boolean mRun = false ;
/**
* TCPClient class constructor, which is created in AsyncTasks after the button click.
* @param mHandler Handler passed as an argument for updating the UI with sent messages
* @param command Command passed as an argument, e.g. "shutdown -r" for restarting computer
* @param ipNumber String retrieved from IpGetter class that is looking for ip number.
* @param listener Callback interface object
*/
public TCPClient(Handler mHandler, String command, String ipNumber, MessageCallback listener) {
this.listener = listener;
this.ipNumber = ipNumber;
this.command = command ;
this.mHandler = mHandler;
}
/**
* Public method for sending the message via OutputStream object.
* @param message Message passed as an argument and sent via OutputStream object.
*/
public void sendMessage(String message){
if (out != null && !out.checkError()) {
out.println(message);
out.flush();
mHandler.sendEmptyMessageDelayed(MainActivity.SENDING, 1000);
Log.d(TAG, "Sent Message: " + message);
}
}
/**
* Public method for stopping the TCPClient object ( and finalizing it after that ) from AsyncTask
*/
public void stopClient(){
Log.d(TAG, "Client stopped!");
mRun = false;
}
魔术发生在这里 - 在&#39; run()&#39;方法。我们在这里使用&#39; try-catch&#39;处理异常的工具(服务器未启用,ip不正确等)。正如您在下面看到的,我们有无限的while()循环来监听传入的消息。我们可以简单地停止它并使用&#39; stopClient()&#39;方法(用于ShutdownAsyncTask&#39; onProgressUpdate&#39;方法)
public void run() {
mRun = true;
try {
// Creating InetAddress object from ipNumber passed via constructor from IpGetter class.
InetAddress serverAddress = InetAddress.getByName(ipNumber);
Log.d(TAG, "Connecting...");
/**
* Sending empty message with static int value from MainActivity
* to update UI ( 'Connecting...' ).
*
* @see com.example.turnmeoff.MainActivity.CONNECTING
*/
mHandler.sendEmptyMessageDelayed(MainActivity.CONNECTING,1000);
/**
* Here the socket is created with hardcoded port.
* Also the port is given in IpGetter class.
*
* @see com.example.turnmeoff.IpGetter
*/
Socket socket = new Socket(serverAddress, 4444);
try {
// Create PrintWriter object for sending messages to server.
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
//Create BufferedReader object for receiving messages from server.
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
Log.d(TAG, "In/Out created");
//Sending message with command specified by AsyncTask
this.sendMessage(command);
//
mHandler.sendEmptyMessageDelayed(MainActivity.SENDING,2000);
//Listen for the incoming messages while mRun = true
while (mRun) {
incomingMessage = in.readLine();
if (incomingMessage != null && listener != null) {
/**
* Incoming message is passed to MessageCallback object.
* Next it is retrieved by AsyncTask and passed to onPublishProgress method.
*
*/
listener.callbackMessageReceiver(incomingMessage);
}
incomingMessage = null;
}
Log.d(TAG, "Received Message: " +incomingMessage);
} catch (Exception e) {
Log.d(TAG, "Error", e);
mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
} finally {
out.flush();
out.close();
in.close();
socket.close();
mHandler.sendEmptyMessageDelayed(MainActivity.SENT, 3000);
Log.d(TAG, "Socket Closed");
}
} catch (Exception e) {
Log.d(TAG, "Error", e);
mHandler.sendEmptyMessageDelayed(MainActivity.ERROR, 2000);
}
}
客户端的最后一件事是Callback接口。我们最后在TCPClient类中有它:
/**
* Callback Interface for sending received messages to 'onPublishProgress' method in AsyncTask.
*
*/
public interface MessageCallback {
/**
* Method overriden in AsyncTask 'doInBackground' method while creating the TCPClient object.
* @param message Received message from server app.
*/
public void callbackMessageReceiver(String message);
}