6 votes

Le Moto G5 Plus ne peut pas se connecter via Socket à un serveur local

J'ai une application où le smartphone doit se connecter via SSLSocket à un serveur local. J'ai testé mon application sur 5 smartphones différents : Moto G2 (6.0), Redmi 3S (6.0.1), LG K5 (6.0), Moto G5 Plus (7.1.1) et OnePlus 5 (8.0). Le Moto G5 Plus est le seul à présenter ce problème.

C'est la ligne qui est à l'origine du comportement problématique. Tous les tests ont été effectués sur le même réseau.

socket = (SSLSocket) sslContext.getSocketFactory().createSocket(serverAddress, serverPort);

Y a-t-il un problème connu avec le Moto G5 Plus ou avec Android 7+ autour de ce comportement ?

EDIT : D'autres tests conduisent à l'idée que le système Android essaie de forcer le Socket à se connecter via le réseau mobile lorsqu'il identifie que l'interface WiFi est connectée, mais sans Internet. Existe-t-il un moyen d'obliger le Socket à utiliser le WiFi au lieu du réseau mobile ?

3voto

lelloman Points 1088

Clause de non-responsabilité : je n'ai pas testé cette méthode et je ne suis pas sûr qu'elle fonctionne.

El Network a un bind(Socket) Peut-être pourriez-vous trouver le réseau wifi et le lier à votre socket. D'après la doc, il semble que c'est ce dont vous avez besoin, il est dit :

/**
 * 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.
 */

El Socket ne devrait pas être connecté avant de se lier au réseau, donc je pense que vous devriez le créer avec socketFactory.createSocket() et le connecter seulement après la fixation.

Donc, vous devez d'abord trouver votre 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
}

ou (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;
    }
}

Ensuite, il faut le lier à la Socket et enfin connect (Kotlin) :

wifiNetwork?.bindSocket(socket)
val socketAddress = InetSocketAddress(hostname, port)
socket.connect(socketAddress)

ou (Java)

if(wifiNetwork != null){
    wifiNetwork.bindSocket(socket);
}
InetSocketAddress socketAddress = InetSocketAddress(hostName, port);
socket.connect(socketAddress);

Note, il faut ACCESS_NETWORK_STATE autorisation

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

2voto

Mrunal Chauhan Points 104

J'espère que cela vous aidera, je viens de trouver votre solution sur Github. Pour plus de détails et pour le lien officiel, veuillez regardez ça je pense que cela peut vous être utile. Si ce n'est pas la réponse, veuillez ignorer cette réponse.

Nous utilisons AsyncTask pour éviter l'erreur fatale StrictMode pour l'accès au réseau (voir les références). La politique StrictMode nous interdit simplement d'affecter sur le Thread 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;
    }   

Dans cet AsyncTask, nous créons un objet TCPClient (expliqué ci-dessous). Dans le constructeur de TCPClient, nous passons l'objet Handler pour changer l'interface utilisateur, COMMAND - la chaîne de caractères avec la commande "shutdown -s" pour arrêter l'ordinateur, le numéro IP - le numéro IP des serveurs ; l'objet Callback - lorsque nous obtenons la réponse des serveurs, la méthode de callback 'messageCallbackReceiver' lance la méthode 'publishProgress', qui publie la progression à la méthode de l'AsyncTask 'onProgressUpdate'.

        /**
         * 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();
            }
        }

Après avoir reçu le message approprié, nous envoyons la commande, ou si nous avons reçu un message erroné, nous envoyons le message "erroné" et arrêtons le client. Après cela, nous sommes transférés vers la méthode 'onPostExecute' :

    @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);

        }
    }

Donc, étape par étape :

->AsyncTask crée l'objet TCPClient.

->Dans le constructeur de TCPClient, nous passons le Handler, la commande, le numéro IP et l'objet Callback.

->Lorsque TCPClient commence la connexion, il envoie le message "shutdown" au serveur.

->Lorsque nous recevons un message du serveur, le callback le transmet à 'onProgressUpdate'.

->Si le message reçu (réponse du serveur) est égal à "shutdown", nous envoyons une COMMANDE au serveur.

->Après l'avoir envoyé, nous arrêtons le client, qui nous transfère à la méthode "onPostExecute".

->Pendant ce temps, le gestionnaire reçoit des messages vides avec des entiers 'msg.what' définis dans MainActivity, qui sont responsables de la mise à jour de l'interface graphique.

Exemple de mise à jour de l'interface utilisateur du widget :

       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;

TCPClient

Cette classe est responsable du maintien de la connexion. Je vais l'expliquer étape par étape :

Dans la première étape, nous pouvons voir les objets passés par ShutdownAsyncTask et autres. De plus, nous pouvons voir les méthodes sendMessage et 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;
        }

La magie se produit ici - dans la méthode "run()". Ici, nous utilisons les outils "try-catch" pour gérer les exceptions (serveur non activé, adresse IP non appropriée, etc.). Comme vous pouvez le voir ci-dessous, nous avons une boucle infinie while() pour écouter les messages entrants. Nous pouvons simplement l'arrêter et finaliser avec la méthode 'stopClient()' (utilisée dans la méthode 'onProgressUpdate' de ShutdownAsyncTask).

    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);

            }

        }

Le dernier élément du client est l'interface Callback. Nous l'avons dans la classe TCPClient à la fin :

    /**
         * 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);
        }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X