Contexte du projet
Cette année, je crois que beaucoup de gens verront diverses machines à poupées, boîtes porte-bonheur, défis de rouge à lèvres et autres machines similaires dans divers centres commerciaux ou cinémas. Sur Douyin, le "Lipstick Challenge", c'est aussi la pagaille. Vous pouvez gagner un rouge à lèvres YSL pour seulement 10 yuans. C'est tentant d'y penser. Peut-être que les programmeurs connaîtront les astuces de ces machines en un coup d'il, mais cette machine s'adresse aux consommateurs sujets à la consommation impulsive, comme les couples, les filles, les adultes avec enfants ; les créatures étranges comme les programmeurs sont généralement ignorées directement haha.
Ensuite, notre société fournit des solutions pour le fonctionnement normal de ces appareils. C'est pourquoi j'ai mon résumé de l'escalade des fosses d'aujourd'hui, hahahaha...
La solution que nous proposons est la suivante : un magasin comprendra les équipements suivants : machine à poupées, défi de rouge à lèvres, classement, contrôle central et, bien sûr, nos services d'arrière-plan. Alors tout d'abord, je vais d'abord vous présenter l'architecture de l'ensemble du système et les responsabilités de chaque appareil :
- Schéma d'architecture système
Obtenez gratuitement des données Android de haut niveau pour rejoindre le groupe : 185873940
- Contexte des services
- Le backend de service n'est pas la responsabilité du côté Android, vous n'avez donc pas besoin d'y prêter trop d'attention.Par rapport à l'équipement du magasin, l'une des responsabilités du backend de service est d'envoyer la qualité de la machine à la machine, surveiller l'état de l'appareil, et quelques autres informations.
- Commande centrale (service local : non connecté au réseau externe) : Cette commande centrale est également un appareil Android, et elle a deux fonctions :
- Machine à griffes : cette pièce contient en fait deux parties, l'une est un appareil Android et l'autre est un appareil matériel.
- Lipstick Challenge : En ce qui concerne le Lipstick Challenge, cet appareil peut être divisé en 3 modules, à savoir, le jeu de couture, le module de programme régulier et le module matériel.
- fusée : Ce programme est un peu spécial, car l'utilisateur ne peut pas le voir. Lorsqu'il quitte l'usine, ce programme est écrit, il est donc responsable du travail suivant :
problème à résoudre
Charger les ressources de manière synchrone
En ce qui concerne la synchronisation des ressources, tout d'abord, examinons les ressources dont nous avons besoin pour synchroniser.Ces ressources sont : le package d'installation apk, les images et les ressources d'index liées à h5.
Comment les ressources sont mises à jour
En ce qui concerne la méthode de mise à jour, il y a en fait un gouffre ici. Au début, la méthode de mise à jour des ressources que nous avons choisie était stupide, et nous avons directement utilisé websocket pour mettre à jour les ressources. Au début, il n'y avait qu'un seul appareil à connecter. Le problème n'est pas Cependant, il a été découvert plus tard que le problème est particulièrement grave lorsque plusieurs appareils sont connectés pour mettre à jour les ressources en même temps, et la connexion est souvent déconnectée, ce qui entraîne l'échec de la mise à jour des ressources. Voici donc la première fosse que j'ai rencontrée. Après avoir trouvé cette fosse, ma méthode de sélection des mises à jour de ressources a été modifiée en : NanoHttpd. NanoHttpd est une bibliothèque open source, implémentée en Java, qui peut créer un serveur Web léger sur les appareils Android. En fait, créer un serveur Web léger sur un appareil Android est la direction que nous devrions choisir dès le début. Pourquoi? Tout d'abord, l'utilisation de NanoHttpd est relativement simple, nous n'avons donc besoin que de quelques lignes de code pour implémenter un serveur web ; deuxièmement, NanoHttpd est relativement stable, ce qui est beaucoup plus stable que lorsque nous utilisons manuellement websocket pour implémenter une distribution de ressources .
Puis après avoir choisi la manière de mettre à jour la ressource, un autre problème est apparu, à propos de l'adresse IP du serveur. Nous savons tous que lorsqu'un appareil Android est connecté à l'Internet mobile ou au Wi-Fi, une adresse IP lui sera automatiquement attribuée, donc cette adresse IP changera. Notre appareil s'éteindra chaque nuit, puis s'allumera et redémarrera le lendemain. Il se verra attribuer une nouvelle adresse IP, de sorte que l'adresse IP du serveur change constamment, donc ce que nous devons faire ici est de trouver un moyen de corriger l'adresse IP d'un certain appareil. Parlons ensuite de NanoHttpd créant un serveur Web léger et comment résoudre le problème des changements d'adresse IP.
NanoHttpd implémente un serveur Web
- Adresse du projet NanoHttpd
- https://github.com/NanoHttpd/nanohttpd
- dépendances de niveau
- Méthode pour réaliser
- Paramètres du constructeur SimpleWebServer
- hôte : adresse IP du serveur
- port : numéro de port (plage de valeurs : 1024~65535)
- wwwroot : répertoire racine où sont placées les ressources statiques
- silencieux : s'il est en mode silencieux
- cors :
- méthode d'entretien
- Sous le même réseau local, nous entrons ensuite l'adresse dans le navigateur : et nous pouvons accéder aux ressources de notre serveur. Bien sûr, le serveur actuellement implémenté est statique et ne peut traiter que get Les demandes de ressources ne peuvent pas traiter d'autres demandes telles que post, put, etc., qui ne peuvent pas être traitées actuellement. Si vous devez traiter d'autres demandes telles que post et put vous-même, vous pouvez vous rendre à l'adresse d'origine du projet et vous référer à son utilisation pour l'implémenter, ici Pas grand chose à dire.
Résoudre le problème des changements d'IP
Dans les appareils Android, l'une de ses adresses IP changera et chaque magasin aura son propre ordinateur de contrôle interne, nous devons donc faire face au problème des changements d'adresse IP. Notre solution comporte les deux étapes suivantes :
Quand la ressource sera-t-elle mise à jour ?
Concernant la mise à jour des ressources, nous devons d'abord clarifier quelles ressources nous devons mettre à jour et comment nous devons mettre à jour.
ressources mises à jour
- Ressource.json
- paquet apk
- Afficher le carrousel dans la machine à poupées
- La ressource h5 de la bannière affichée dans la machine à griffes
fichier de configuration mis à jour
- Toutes les données sur nos ressources et les nouvelles sont stockées dans le dossier Resource.json, puis nous obtenons Resource.json du serveur de contrôle central (dans le réseau local) toutes les 5 minutes, puis chaque type de ressource est écrit dans le les données dans Resource.json sont évaluées. Ensuite, l'implémentation et le contenu spécifique écrit dans le fichier Resource.json sont les suivants :
Mise à jour du fichier de ressource d'image et de bannière de ressource
- Les méthodes de mise à jour des fichiers de ressources des images et des bannières sont similaires, sauf que les chemins de stockage ne sont pas dans le même répertoire. Ensuite, pour la mise à jour de ces ressources, nous jugeons par la valeur de hachage et le nom de fichier de la ressource technique. La machine à poupée ou le dispositif de défi de rouge à lèvres obtiendra le fichier Resourse.json du contrôle central toutes les 5 minutes, puis retirera le ResListModel, qui a été introduit auparavant et est le fichier de configuration pour enregistrer les mises à jour des ressources ; puis nous retirons la configuration relative à partir de celui-ci, d'abord selon le fichier Le nom détermine si le fichier existe déjà localement. S'il n'existe pas, il est directement ajouté à la liste des mises à jour des ressources. S'il existe, on juge si la valeur de hachage est la même. S'il s'agit du même, il ne sera pas mis à jour. Ajoutez-le simplement à la liste des ressources mises à jour.
- Organigramme de mise à jour des ressources d'image et de bannière :
Obtenez gratuitement des données Android de haut niveau pour rejoindre le groupe : 185873940
- Contrôle central des ressources informatiques de votre valeur de hachage
essayer {
// fichier de ressource de bannière
String fileName = fileFilter.getAbsolutePath().substring(baseDirLength);
RandomAccessFile randomAccessFile = new RandomAccessFile(fileFilter,"r");
octet buf = nouvel octet ;
randomAccessFile.read(buf);
randomAccessFile.close();
MessageDigest md5 = MessageDigest.getInstance("md5");
hachage d'octets = md5.digest(buf);
Chaîne hashStr = ByteToHex(hash,0,hash.length);
res.bannerFiles.put(hashStr,fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
} capture (IOException e) {
e.printStackTrace();
}
// les octets sont convertis en hexadécimal
public static String ByteToHex(byte bt, int offset, int len) {
StringBuffer sb = new StringBuffer();
pour (int je = décalage; je < décalage + longueur; i++) {
int tmp = bt et 0xff ;
String tmpStr = Integer.toHexString(tmp);
si (tmpStr.length() < 2)
sb.append("0");
sb.append(tmpStr);
}
return sb.toString().toUpperCase();
}
- Le dispositif de machine à griffes vérifie les mises à jour (par exemple : fichier de ressources de bannière)
problème de mise à jour du programme
Concernant la mise à jour du programme, c'est beaucoup plus simple que la mise à jour des ressources images.
- Les étapes de notre mise à jour de la version d'implémentation sont les suivantes :
- Étape 1 : recherchez le fichier apk qui existe localement (l'apk de l'appareil a un chemin et un nom de fichier spécifiés) et supprimez-le.
- étape 2 : déterminez si le numéro de version du package d'installation dans le contrôle central est supérieur au numéro de version du programme local, et si c'est le cas, entrez l'étape 3 ; sinon, ignorez-le et aucune mise à niveau du programme n'est requise
- étape 3 : Téléchargez la dernière version du package d'installation apk
- étape 4 : Une fois le téléchargement réussi, envoyez une diffusion (action : nom du package ; extra : chemin du fichier apk) au programme de fusée
- step5 : Le programme de fusée met à jour le programme après avoir reçu la diffusion
- Organigramme de mise à niveau du programme
Obtenez gratuitement des données Android de haut niveau pour rejoindre le groupe : 185873940
- implémentation de code spécifique
public statique Observable < booléen > updateGame(ResListBean res) lance IOException, InterruptedException {
Liste des tableaux < Dossier > apkList = new ArrayList();
FileUtils.GetFilesInDir(resDir, apkList, nouvelle chaîne{
".apk",
});
// supprimer le package apk existant localement
pour (fichier fichier : apkList) {
fichier.delete();
}
fais {
if (res.UpdateApk == null || res.UpdateApkVersion == null) {
Pause;
}
// Déterminez si vous devez mettre à jour
si (BuildConfig.VERSION_CODE > = res.UpdateApkVersionCode) {
Pause;
}
// URL de l'apk
URL de chaîne finale = nouvelle URL ("http", Config.instance.centralServerAddress, Config.instance.httpPort, nouveau fichier (BuildConfig.APPLICATION_ID, res.UpdateApk).getAbsolutePath()).toString();
MainActivity.appendAndScrollLog(String.format("Télécharger le fichier de mise à jour %s\n", url));
// télécharger le fichier apk
return DownLoadUtils.getDownLoadFile(url,resDir.getAbsolutePath(),res.UpdateApk)
.subscribeOn(Planificateurs.io())
.observeOn(Planificateurs.io())
.flatMap (nouvelle fonction < Fichier, ObservableSource < Chaîne de caractères > > () {
@Passer outre
public ObservableSource < Chaîne de caractères > appliquer (fichier fichier) lance une exception {
chemin de chaîne = file.getAbsolutePath();
MainActivity.appendAndScrollLog(String.format("Téléchargement du fichier de mise à jour terminé %s %s\n", chemin, url));
PackageManager pm = MainActivity.instance.getPackageManager();
PackageInfo pi = pm.getPackageArchiveInfo(chemin, 0);
si (pi == nul) {
MainActivity.appendAndScrollLog(String.format("Impossible d'ouvrir le fichier de mise à niveau %s\n", chemin));
return Observable.just("");
}
MainActivity.appendAndScrollLog(String.format("Comparaison des fichiers de mise à niveau : natif(%s %s)/distant(%s %s)\n", BuildConfig.APPLICATION_ID, BuildConfig.VERSION_NAME, pi.packageName, pi.versionName) );
si (!BuildConfig.APPLICATION_ID.equals(pi.packageName)
|| BuildConfig.VERSION_CODE > = pi.versionCode) {
return Observable.just("");
}
return Observable.just(path);
}
})
.flatMap (nouvelle fonction < Chaîne, observable < booléen > > () {
@Passer outre
public observable < booléen > apply(String updateApk) lève une exception {
si (!updateApk.isEmpty()) {
Log.e(TAG, "Attendez que le fichier de mise à jour soit installé après la fin du jeu...");
MainActivity.appendAndScrollLog("Attendre la fin du jeu pour installer le fichier de mise à jour...\n");
synchronisé (GamePlay.class) {// Empêche la mise à jour de la version pendant que le jeu est en cours d'exécution
Log.e(TAG, "Post diffusion");
Intention intent = new Intent();
intention.setAction(Config.updateBroadcast);
intention.putExtra("apk", updateApk);
MainActivity.instance.sendBroadcast(intention);
System.exit(0);
}
}
retourner Observable.just(true);
}
});
} tandis que (faux);
retourner Observable.just(true);
}
Téléchargement du fichier de ressources
Concernant le téléchargement des fichiers ressources, je choisis okdownload. okdownload est un moteur de téléchargement qui prend en charge le multithreading, le multitâche, le téléchargement avec reprise, fiable, flexible, performant et puissant. Pour plus de détails, consultez l'adresse okdownload GitHub
- manière dépendante
- Exemple simple et pratique
Téléchargement de fichier unique
Tâche DownloadTask = new DownloadTask.Builder(url, parentFile) .setFilename (nom de fichier) // l'intervalle minimal en millisecondes pour la progression du rappel .setMinIntervalMillisCallbackProcess(30) // retélécharge même si la tâche a déjà été effectuée dans le passé. .setPassIfAlreadyCompleted(false) .construire(); tâche.enqueue(écouteur); // annuler tâche. annuler(); // exécute la tâche synchronisée tâche.execute(écouteur);Téléchargement de plusieurs fichiers
tâches DownloadTask finales = nouvelle DownloadTask ; tâches = new DownloadTask.Builder("url1", "path", "filename1").build(); tâches = new DownloadTask.Builder("url2", "path", "filename1").build(); DownloadTask.enqueue(tâches, écouteur);- Combiné avec Rxjava pour réaliser le téléchargement de fichiers
Bloquer les menus déroulants et les barres de navigation inférieures
Des appareils comme la machine à poupées et la machine à treillis sont directement orientés vers les utilisateurs hors ligne, nous ne pouvons donc pas montrer tous nos appareils Android à nos utilisateurs, nous devons imposer certaines restrictions sur le comportement de l'utilisateur, comme interdire aux utilisateurs d'utiliser la barre de navigation ou Les menus déroulants quittent le programme en cours, les empêchant d'effectuer certaines opérations dangereuses. Ma solution consiste à définir le programme de fusée actuel comme application de démarrage et de bureau par défaut, et à désactiver le programme de lancement et le programme systemui fournis avec l'appareil Android, afin que notre application de fusée soit lancée au démarrage de l'appareil et interdit avec succès aux utilisateurs de utiliser la barre de navigation et le menu déroulant pour effectuer des opérations illégales.
- Recherchez le nom de package correspondant du programme de lancement et du programme systemui fourni avec l'appareil Android
- Nous pouvons utiliser adb shell pm list packages pour connaître la liste des programmes installés sur l'appareil, principalement affichés par nom de package.
- Trouver le nom du package du programme de lancement , découvrez le nom du package : com.android.launcher3
- Trouver le nom du package du programme systemui : Découvrez le nom du package : com.android.systemui
- Interdire l'utilisation du programme de lancement et du programme systemui fourni avec les appareils Android
- Interdire l'utilisation du programme de lancement
- Désactiver l'utilisation des programmes systemui
- L'implémentation du code interdit l'utilisation du programme de lancement et du programme systemui fourni avec l'appareil Android
Mise en uvre de l'IdO
Concernant la mise en uvre de l'IoT, nous utilisons ici le service "Micro Message Queue for IoT" d'Ali. Concernant le service "Micro Message Queue for IoT", l'explication d'Ali est la suivante :
Micro Message Queue pour IoT est un sous-produit de Message Queuing (MQ). En réponse aux exigences particulières de transmission de messages des utilisateurs de l'Internet mobile et de l'Internet des objets, Message Queue (MQ) a ouvert la prise en charge complète du protocole MQTT en lançant Micro Message Queue pour l'IoT
- Protocole MQTT ?
- Le nom complet de MQTT est : Message Queuing Telemetry Transport, qui est un protocole de messagerie instantanée léger basé sur le modèle de publication-abonnement. La conception du protocole est ouverte, le protocole est simple et la plate-forme est riche en support.Il peut connecter presque tous les éléments Internet au monde extérieur, il présente donc de nombreux avantages dans le domaine de l'Internet mobile et de l'Internet des objets.
- Fonctionnalités de MQTT
- Utilisez le mode de message de publication/abonnement (Pub/Sub) pour fournir une distribution de messages un à plusieurs et libérer le couplage entre les applications ;
- Transmission de message masquée au contenu de la charge utile ;
- Utilisez TCP/IP pour fournir une connectivité réseau de base ;
- Il existe trois niveaux de services de messagerie ;
- Petits transferts avec peu de surcharge (la longueur de l'en-tête est fixée à 2 octets) et la commutation de protocole est minimisée pour réduire le trafic réseau.
- Explication des termes clés Nom Explication Parent Topic Le protocole MQTT est basé sur le modèle Pub/Sub, donc tout message appartient à un Topic. Selon le protocole MQTT, il existe plusieurs niveaux de rubriques et la rubrique de premier niveau est définie comme la rubrique parent (Sujet parent). Avant d'utiliser MQTT, la rubrique parent doit être créée dans la console MQ. Sous-rubrique La rubrique de second niveau de MQTT et même la rubrique de troisième niveau sont des sous-classes sous la rubrique parent. Lors de l'utilisation, définissez-le directement dans le code sans le créer. Il convient de noter que MQTT limite la longueur totale de la rubrique parent et de la sous-rubrique à 64 caractères. Le dépassement de la limite de longueur entraînera une exception client. ID client L'ID client de MQTT est l'identifiant unique de chaque client, qui doit être unique au monde. L'utilisation du même ID client pour se connecter au service MQTT sera rejetée.
Implémentation de l'iot dans Android
Le processus de mise en uvre de l'affichage de la connexion iot est le suivant : d'abord, nous générons le triplet de l'appareil à partir de l'arrière-plan de gestion par lots, et le format du nom de fichier est deviceName.json (Par exemple : 00001.json), qui est la triple information sur chaque appareil ; puis nous insérons le disque U contenant le triple fichier dans l'appareil Android (machine à griffes ou défi de rouge à lèvres) ; le programme de fusée détectera automatiquement l'Insérez le U disque et coupez le fichier dans le répertoire spécifié de l'appareil Android ; l'appareil Android peut alors lire les informations du triplet dans le fichier spécifié ; enfin, utilisez ce triplet pour connecter mqtt.
- ajouter des dépendances
- À propos des triplés
- Trois choses dont il faut se préoccuper dans les appareils Android, les trois éléments nécessaires dans le protocole mqtt pour identifier un appareil, s'il y a les mêmes triplets, il doit y avoir une erreur, entraînant une déconnexion et une reconnexion fréquentes de mqtt. Le triplet est principalement généré dans l'arrière-plan de gestion d'Ali, et il ne doit être utilisé que du côté de l'appareil Android.
- L'attribut est utilisé productKey correspond à la clé du programme, similaire à appid deviceName correspond à l'ID client ci-dessus, utilisé pour identifier de manière unique le deviceSecret d'un appareil Android Utilisez l'algorithme HmacSHA1 pour calculer la chaîne de signature et définissez la chaîne de signature sur le paramètre Mot de passe pour l'authentification
- sujet sur les abonnements
- Les sujets sont définis dans la gestion d'arrière-plan d'Alibaba Cloud, et nous envoyons et recevons des messages via ces sujets.
- Code pour implémenter la connexion iot
- Couper le triple fichier de configuration
- Lire les informations du fichier de configuration du chemin spécifié (triple)
public static File configFile = new File(new File(Environment.getExternalStorageDirectory(), "WejoyRes"), "Config.json");
static void read() lance IOException {
si (configFile.exists()) {
RandomAccessFile in = new RandomAccessFile(configFile, "r");
octet buf = nouvel octet ;
in.read(buf);
joindre();
instance = JsonIterator.deserialize(new String(buf, "utf-8"), Config.class);
} autre {
instance = nouvelle configuration();
}
mqttRequestTopic = String.format("/sys/%s/%s/rrpc/request/", instance.productKey, instance.deviceName);
mqttResponseTopic = String.format("/sys/%s/%s/rrpc/response/", instance.productKey, instance.deviceName);
mqttPublishTopic = String.format("/%s/%s/update", instance.productKey, instance.deviceName);
}
- connecter mqtt
- Envoyer un message:
- Lors de l'envoi d'un message, nous devons spécifier le sujet, sinon le serveur ne peut pas recevoir notre message.
- Recevoir un message : Lors de la réception d'un message, nous devons également déterminer de quel sujet il provient. À l'exception du sujet que nous avons spécifié, nous ne traitons pas d'autres sujets ; lorsque nous recevons un message du serveur, nous déterminons d'abord le type de message, puis réagissons différemment selon le type correspondant. Par exemple, lorsque nous recevons une instruction pour attribuer un score supérieur à la machine à poupée à partir de la demande d'arrière-plan, nous envoyons une commande de score supérieur au module matériel de l'appareil, attendons que l'appareil réponde et envoyons un message de réponse à l'arrière-plan. Ce message de réponse doit être terminé dans le délai spécifié, sinon il est considéré comme expiré.
Communication Android et matérielle
Dans les deux appareils de la machine à poupée et du défi du rouge à lèvres, nous avons tous besoin de communiquer avec l'appareil, tels que : pièce de monnaie de la machine à poupée, retour d'information sur les cadeaux de la machine à poupée, pression sur la grille de rouge à lèvres sélectionnée, etc. Ils doivent tous communiquer avec les modules matériels. .communication. En termes de sélection de trame pour la communication série, nous choisissons principalement l'api android-serialport-api de Google à implémenter. Adresse d'origine du projet
- manière dépendante
- Modifier le chemin su
- Méthode de connexion
Lors de la connexion au port série, vous devez spécifier le numéro de port série et le débit en bauds, puis traiter régulièrement les instructions envoyées par la machine.
static void init() lance IOException { SerialPort.setSuPath("/system/xbin/su"); // Définir le numéro de port série et le débit en bauds serialPort = nouveau SerialPort(Config.serialPort, Config.baudrate); // reçoit le flux de commandes inputStream = serialPort.getInputStream(); // envoie le flux de commandes outputStream = serialPort.getOutputStream(); // Traiter les informations machine toutes les 100 ms Observable.interval(100, TimeUnit.MILLISECONDS) .observeOn(serialScheduler) .subscribe(nouveau Consommateur < Long > () { @Passer outre public void accept(Long aLong) lance une exception { // traite la commande envoyée par la machine handleRecv(); } }); }- Envoyer des instructions à la machine
Lors de l'envoi d'instructions à la machine, il est implémenté en conjonction avec Rxjava. De plus, l'envoi d'instructions à la machine doit avoir un format prescrit (protocole de communication formulé en interne). Les données que nous envoyons et recevons sont un tableau d'octets, notre format doit donc être strictement conforme au protocole que nous avons formulé, comme suit Voici un exemple simple d'une machine à poupée à pièces :
ObservableSource statique < MqttMessage > addCoins(final AddCoinsDownstream msg) {
return Observable.create(new ObservableOnSubscribe < MqttMessage > () {
@Passer outre
public void subscribe(ObservableEmitter < MqttMessage > émetteur) lance une exception {
currentUser = msg.u ;
currentHeadUrl = msg.h;
currentNickname = msg.nk;
octet buf = nouvel octet{0x11, addCoinsCmd, msg.num, msg.c, 0, 0x00, 0x00} ;
octet ret = signe(buf);
essayer {
outputStream.write(ret);
} capture (IOException e) {
e.printStackTrace();
}
en attenteCmd = addCoinsCmd ;
en attendantEmitter = émetteur ;
}
})
.subscribeOn(serialScheduler);
}
- recevoir les instructions de la machine
La partie de réception des messages de la machine est effectuée toutes les 100 ms. Lors du traitement des instructions de la machine, il est nécessaire de filtrer d'abord les octets invalides, puis de traiter les messages selon le protocole que nous avons établi pour déterminer s'il s'agit de la machine à poupée ou du jeu. résultat, etc., et enfin effectuer un contrôle CRC16 sur les données renvoyées par la machine.
statique vide handleRecv() { essayer { pour (; ; ) { int len = inputStream.available(); si (len < = 0) { Pause; } len = inputStream.read(buf, bufReadOffset, buf.length - bufReadOffset); //Log.d("serialPort", String.format("read: %s", byteToHex(buf, bufReadOffset, len))); bufReadOffset += len; pour (; ; ) { si (bufParseEnd == -1) { for (; bufParseStart < bufReadOffset; bufParseStart++) { si (buf == (octet) 0xAA) { bufParseEnd = bufParseStart + 1; Pause; } } } si (bufParseEnd != -1) { pour (; bufParseFin < bufReadOffset; bufParseEnd++) { si (buf == (octet) 0xAA) { bufParseStart = bufParseEnd; bufParseEnd += 1; Continuez; } si (buf == (octet) 0xDD) { si (bufParseEnd - bufParseStart > = 5) { bufParseEnd += 1; taille en octets = buf ; indice d'octet = buf ; octet cmd = buf; contrôle d'octet = (octet) (taille ^ index ^ cmd); pour (int je = bufParseStart + 4; je < bufParseEnd - 2; i++) { vérifier ^= buf ; } si (vérifier == buf) { //Log.d("serialPort", String.format("protocole : %s, taille : %d, index : %d, cmd : %d, vérification : %d, données : %s", byteToHex(buf, bufParseStart, bufParseEnd - bufParseStart), taille, index, cmd, vérification, byteToHex(buf, bufParseStart + 4, taille - 3))); commutateur (cmd) { // battement de coeur cas heartBeatCmd : { } Pause; // meilleur score cas addCoinsCmd : { } Pause; // résultat du jeu cas gameResultCmd : { cadeau booléen = buf != 0; IoT.sendGameResult(cadeau); si (cadeau) { // Envoi des informations utilisateur au contrôle central pour l'affichage du classement WSSender.getInstance().sendUserInfo(currentUser, currentHeadUrl, currentNickname); } } Pause; défaut: Pause; } } } bufParseStart = bufParseEnd; bufParseEnd = -1; Pause; } } } si (bufParseStart > = bufReadOffset || bufParseEnd > = bufReadOffset) { Pause; } } if (bufReadOffset == buf.length) { System.arraycopy(buf, bufParseStart, buf, 0, bufReadOffset - bufParseStart); si (bufParseEnd != -1) { bufParseEnd -= bufParseStart; bufReadOffset = bufParseEnd; } autre { bufReadOffset = 0 ; } bufParseStart = 0; } } } capture (IOException e) { e.printStackTrace(); } }communication par socket Web
Dans le mode de communication entre la commande centrale et la machine à poupées, nous choisissons websocket. Le terminal de contrôle central est le serveur, puis la machine à griffes est le client.
serveur
- Mise en uvre du serveur : à l'heure actuelle, la mise en uvre du serveur consiste uniquement à recevoir les données en retour de la machine à poupées, il n'y a donc pas d'opération compliquée.
- Simple à utiliser
client
Côté client, les caractères à effectuer incluent actuellement la déconnexion, la reconnexion et la transmission de données. Lors de la déconnexion et de la reconnexion, cela doit être fait dans un nouveau sous-thread, sinon l'erreur suivante sera signalée :
Vous ne pouvez pas initialiser une reconnexion à partir du thread websocket. Utilisez la reconnexion dans un autre thread pour assurer un nettoyage réussiPar conséquent, chaque fois que nous nous déconnectons et redémarrons, nous devons le faire dans un nouveau thread enfant. De plus, lors de l'envoi de données, si le socket n'est pas connecté, l'envoi de données signalera une exception, donc lorsque nous aurons des données à envoyer, si le socket n'est pas connecté, mettez-le en cache localement et attendez que le socket soit connecté. téléchargement, les données bloquées sont envoyées en une seule fois.
- Paramétrage des dépendances
- WSClient.java
- WSSender.java
/**
* Créé par runla le 2018/10/26.
* Description du fichier : expéditeur du message pour Websocket
*/
classe publique WSSender {
chaîne finale statique privée TAG = "WSSender" ;
public statique final int MAX_MESSAGE_COUNT = 128 ;
instance WSSender statique privée ;
mWSClientManager privé ;
// file d'attente de messages
liste liée privée < Chaîne de caractères > mMessageList = nouvelle liste liée < > ();
expéditeur WSS privé() {
}
public statique WSSender getInstance() {
si (instance == null) {
synchronisé (WSSender.class) {
si (instance == null) {
instance = new WSSender();
}
}
}
instance de retour ;
}
public void setWSClient(WSClient wsClientManager) {
this.mWSClientManager = wsClientManager ;
}
/**
* Envoyer tous les messages bloqués
*/
public void clearAllMessage() {
si (mWSClientManager == null) {
revenir;
}
tandis que (mMessageList.size() > 0
mMessageList.getFirst() != null) {
Log.d(TAG, "sendMessage : " + mMessageList.size());
mWSClientManager.send(mMessageList.getFirst());
mMessageList.removeFirst();
}
}
/**
* Envoyez un message, si le message ne peut pas être envoyé, attendez que la connexion réussisse et essayez de l'envoyer à nouveau
*
* @param msg
* @revenir
*/
public booléen sendMessage(String msg) {
si (mWSClientManager == null) {
throw new NullPointerException("le client websocket est nul");
}
si (TextUtils.isEmpty(msg)) {
retourner faux ;
}
// Ajoute les données à envoyer à la fin de la file d'attente
mMessageList.addLast(msg);
tandis que (mMessageList.size() > 0
mMessageList.getFirst() != null) {
Log.d(TAG, "sendMessage : " + mMessageList.size());
si (!mWSClientManager.isOpen()) {
// essaie de se reconnecter
mWSClientManager.onReconnect();
Pause;
} autre {
mWSClientManager.send(mMessageList.getFirst());
mMessageList.removeFirst();
}
}
// Si la file d'attente de messages dépasse la capacité maximale que nous avons définie, supprimez le premier message ajouté
si (mMessageList.size() > = MAX_MESSAGE_COUNT) {
mMessageList.removeFirst();
}
retourner faux ;
}
}
- WSReceiver.java
/**
* Créé par runla le 2018/10/26.
* Description du fichier : Récepteur de message Websocket
*/
public class WSReceiver {
mWSClientManager privé ;
WSSender privé mWSSender ;
privé OnMessageCallback onMessageCallback ;
public WSReceiver() {
}
public void setWSClient(WSClient mWSClientManager) {
this.mWSClientManager = mWSClientManager ;
}
public void setWSSender(WSSender mWSSender) {
this.mWSSender = mWSSender ;
}
/**
* Gérer les messages entrants
* Message @param
*/
public void handlerMessage(chaîne message){
si (onMessageCallback != null){
onMessageCallback.onHandlerMessage(message);
}
}
public void setOnMessageCallback(OnMessageCallback onMessageCallback) {
this.onMessageCallback = onMessageCallback ;
}
interface publique OnMessageCallback{
void onHandlerMessage (message de chaîne);
}
}
- connecter un appel
- envoi de données
stockage de base de données
Dans le terminal de contrôle central, nous devons afficher la version de classement, qui est utilisée pour afficher le classement des poupées dans les poupées des utilisateurs dans les clips ce mois-ci et cette semaine, nous devons donc enregistrer le nombre de poupées dans le les clips de l'utilisateur et d'autres informations personnelles sur le terminal de contrôle central.Information, GreenDAO est un framework ORM léger et rapide orienté Android open source, qui mappe les objets Java aux bases de données SQLite.Lorsque nous exploitons la base de données, nous n'avons plus besoin d'écrire du SQL complexe En termes de performances, GreenDAO a implémenté une solution hautement optimisée, avec une surcharge de mémoire minimale, de petites dépendances et une prise en charge du chiffrement de la base de données. Concernant l'utilisation de GreenDAO, je ne le ferai pas ici, pour une utilisation spécifique, merci de vous référer au site officiel GreenDAO.
écrire à la fin
En ce qui concerne la construction de l'ensemble du système, j'ai rencontré de nombreux pièges. Voici quelques-unes des solutions que j'ai fournies pour ce projet. Il est impossible d'écrire toutes les solutions actuelles. Ce projet a actuellement des magasins à Xi'an, Chengdu et ailleurs Cliquez, selon les commentaires, le profit est énorme, mais la période de bonus de ce type de projet ne sera pas trop longue, on estime qu'elle est d'environ 2 à 3 ans.
Ici, j'ai mis du matériel d'apprentissage sur Android à la fin, dans l'espoir d'aider tout le monde.
1. Essentiels Avancés Avancés
2. Réglage complet des performances de l'application Android
3. Développement mixte de l'applet WeChat et Hybird
Vous voulez obtenir des informations Android plus avancées ? Vous souhaitez améliorer votre technique ? Suivez + message privé pour répondre "Données Android" gratuitement !