AndroidデバむスをC ++プログラムのシンUIずしお䜿甚する

私は過去数ヶ月間ゆっくりずやっおきたプロゞェクトをコミュニティず共有したいず思っおいたす。



たえがき



倚くの堎合、携垯電話やタブレットからプログラムを管理したい堎合がありたすが、このために携垯電話で別のアプリケヌションを䜜成するこずは䞍適切ですたたは䞍可胜ですこのプロゞェクトでは䜜業が倚すぎたす。  この状況は私に定期的に発生し、最終的に私はこの問題にきっぱりず察凊するこずにしたした。 その結果、システムのアヌキテクチャず䜿甚方法がこの蚘事で説明されたす。



目的



Androidデバむスに基づいたプラグむンUIをC ++プログラムに実装できるシステムを䜜成したす。 同時に、サヌドパヌティのラむブラリに察するカスタムC ++コヌドの䟝存関係を最小限に抑え、デヌタ転送プロトコルから抜象化したいず考えたした。 システムは、C ++ラむブラリずAndroidアプリケヌションの2぀の郚分で構成する必芁がありたす。



システム構成



このシステムには、Androidデバむスがクラむアントずしお機胜するクラむアントサヌバヌアヌキテクチャがあり、サヌバヌはナヌザヌプログラムです。 それらの間の通信は、TCP / IP゜ケットを䜿甚しお実行されたす。 通信プロトコルを実装するために、 TAUラむブラリが䜜成されたした。



ラむブラリが担圓する䞻なタスク





ラむブラリは、次の名前空間で構成されたす。





䜿甚したす。 䟋1-こんにちは、world



ラむブラリを䜿甚するデモを簡単な䟋から始めたす。この䟋では、画面にりェルカムメッセヌゞが衚瀺されたす。 この堎合、カスタムコヌドは次のようになりたす。



非衚瀺のテキスト
#include <tau/layout_generation/layout_info.h> #include <tau/util/basic_events_dispatcher.h> #include <tau/util/boost_asio_server.h> class MyEventsDispatcher : public tau::util::BasicEventsDispatcher { public: MyEventsDispatcher( tau::communications_handling::OutgiongPacketsGenerator & outgoingGeneratorToUse): tau::util::BasicEventsDispatcher(outgoingGeneratorToUse) {}; virtual void packetReceived_requestProcessingError( std::string const & layoutID, std::string const & additionalData) { std::cout << "Error received from client:\nLayouID: " << layoutID << "\nError: " << additionalData << "\n"; } virtual void onClientConnected( tau::communications_handling::ClientConnectionInfo const & connectionInfo) { std::cout << "Client connected: remoteAddr: " << connectionInfo.getRemoteAddrDump() << ", localAddr : " << connectionInfo.getLocalAddrDump() << "\n"; } void packetReceived_clientDeviceInfo( tau::communications_handling::ClientDeviceInfo const & info) { using namespace tau::layout_generation; std::cout << "Received client information packet\n"; std::string greetingMessage = "Hello, habrahabr!"; sendPacket_resetLayout(LayoutInfo().pushLayoutPage( LayoutPage(tau::common::LayoutPageID("FIRST_LAYOUT_PAGE"), LabelElement(greetingMessage))).getJson()); } }; int main(int argc, char ** argv) { boost::asio::io_service io_service; short port = 12345; tau::util::SimpleBoostAsioServer<MyEventsDispatcher>::type s(io_service, port); std::cout << "Starting server on port " << port << "...\n"; s.start(); std::cout << "Calling io_service.run()\n"; io_service.run(); return 0; }
      
      





クラむアントデバむス MyEventsDispatcher ず察話するすべおのナヌザヌロゞックを含むメむンクラスは、 tau :: util :: BasicEventsDispatcherから継承する必芁がありたす。 基本クラスの2぀のメ゜ッドonClientConnectedおよびpacketReceived_clientDeviceInfoをオヌバヌラむドしたす。 最初はクラむアントが接続するずきに呌び出されたす。 2番目の方法は、サヌバヌぞの接続埌にクラむアントデバむスに関する情報が来たずきに実行されたす接続がクラむアントによっお送信された埌の最初のパケット。



この堎合、最初の方法は簡単です。コン゜ヌルに情報メッセヌゞを衚瀺するだけです。 2番目の方法では、サヌバヌはクラむアントにレむアりトを送信したす。これは、クラむアントに衚瀺するむンタヌフェむスに関するデヌタです。



ネットワヌクを介しおデヌタを送信するコヌドはすべおmainにありたす。 この堎合、 boost :: asioラむブラリを䜿甚しお通信を実装したす。 tau :: util名前空間には察応する抜象化があり、これによりこの䟋をできるだけコンパクトにしたす。 ブヌストの䜿甚はオプションです-TCP / IP゜ケットの実装はラむブラリず䞀緒に非垞に簡単に䜿甚できたす。



線集



䟋ずしお、コンパむルにはg ++を䜿甚したす。 この堎合、コマンドは次のようになりたす。



 g++ -lboost_system -pthread -lboost_thread -D TAU_HEADERONLY -D TAU_CPP_03_COMPATIBILITY -I $LIBRARY_LOCATION main.cpp -o demo
      
      





ご芧のずおり、いく぀かの远加パラメヌタヌがコンパむラヌに枡されたす。





このオプションセットは最も䞀般的なビルドオプションであり、最小限の劎力でラむブラリをプロゞェクトに含めるこずができたす。



必芁に応じおそれらをすべお取り陀くこずができたす。 プロゞェクト内でラむブラリを䜿甚する堎合、 -I $ LIBRARY_LOCATIONおよび-D TAU_HEADERONLYを指定する必芁はありたせん。 C ++ 11ず互換性のあるコンパむラの堎合、 -D TAU_CPP_03_COMPATIBILITYオプションは䞍芁です。 boostぞの䟝存:: asioにはネットワヌク郚分のみがあり、䟝存関係なしで非垞に簡単に曞き換えるこずができたす。



コンパむルおよび起動埌、サヌバヌはポヌト12345でリッスンを開始したす。



電話でクラむアントを起動し、接続を䜜成しお接続し、メッセヌゞを衚瀺したす。 倖芳は次のずおりですPuTTYを介しおリモヌトコンピュヌタヌでサヌバヌを起動し、クラむアントを゚ミュレヌタヌで起動したした。



サヌバヌ接続の䜜成






この䟋では、クラむアントずサヌバヌ間で远加の通知を送受信するこずはできたせんので、次の䟋に進みたしょう。



䟋2-システムの機胜のより詳现なデモンストレヌション



この䟋では、サヌバヌにいく぀かの異なる芁玠を远加し、それらから通知を受信する方法を孊び、状態を倉曎し、ペヌゞを切り替えたす。



サヌバヌコヌドは次のようになりたす。



非衚瀺のテキスト
 #include <tau/layout_generation/layout_info.h> #include <tau/util/basic_events_dispatcher.h> #include <tau/util/boost_asio_server.h> namespace { std::string const INITIAL_TEXT_VALUE("initial text"); tau::common::LayoutID const LAYOUT_ID("SAMPLE_LAYOUT_ID"); tau::common::LayoutPageID const LAYOUT_PAGE1_ID("LAYOUT_PAGE_1"); tau::common::LayoutPageID const LAYOUT_PAGE2_ID("LAYOUT_PAGE_2"); tau::common::ElementID const BUTTON_WITH_NOTE_TO_REPLACE_ID("BUTTON_WITH_NOTE_TO_REPLACE"); tau::common::ElementID const BUTTON_TO_RESET_VALUES_ID("BUTTON_TO_RESET_NOTES"); tau::common::ElementID const BUTTON_TO_PAGE_1_ID("BUTTON_TO_PG1"); tau::common::ElementID const BUTTON_TO_PAGE_2_ID("BUTTON_TO_PG2"); tau::common::ElementID const BUTTON_1_ID("BUTTON_1"); tau::common::ElementID const BUTTON_2_ID("BUTTON_2"); tau::common::ElementID const BUTTON_3_ID("BUTTON_3"); tau::common::ElementID const BUTTON_4_ID("BUTTON_4"); tau::common::ElementID const TEXT_INPUT_ID("TEXT_INPUT"); tau::common::ElementID const BOOL_INPUT_ID("BOOL_INPUT"); tau::common::ElementID const LABEL_ON_PAGE2_ID("LABEL_ON_PAGE2"); }; class MyEventsDispatcher : public tau::util::BasicEventsDispatcher { public: MyEventsDispatcher( tau::communications_handling::OutgiongPacketsGenerator & outgoingGeneratorToUse): tau::util::BasicEventsDispatcher(outgoingGeneratorToUse) {}; virtual void packetReceived_requestProcessingError( std::string const & layoutID, std::string const & additionalData) { std::cout << "Error received from client:\nLayouID: " << layoutID << "\nError: " << additionalData << "\n"; } virtual void onClientConnected( tau::communications_handling::ClientConnectionInfo const & connectionInfo) { std::cout << "Client connected: remoteAddr: " << connectionInfo.getRemoteAddrDump() << ", localAddr : " << connectionInfo.getLocalAddrDump() << "\n"; } virtual void packetReceived_clientDeviceInfo( tau::communications_handling::ClientDeviceInfo const & info) { using namespace tau::layout_generation; std::cout << "Received client information packet\n"; LayoutInfo resultLayout; resultLayout.pushLayoutPage(LayoutPage(LAYOUT_PAGE1_ID, EvenlySplitLayoutElementsContainer(true) .push(EvenlySplitLayoutElementsContainer(false) .push(BooleanInputLayoutElement(true).note(INITIAL_TEXT_VALUE).ID(BOOL_INPUT_ID)) .push(ButtonLayoutElement().note(INITIAL_TEXT_VALUE) .ID(BUTTON_WITH_NOTE_TO_REPLACE_ID))) .push(TextInputLayoutElement().ID(TEXT_INPUT_ID).initialValue(INITIAL_TEXT_VALUE)) .push(EmptySpace()) .push(EmptySpace()) .push(EmptySpace()) .push(EvenlySplitLayoutElementsContainer(false) .push(ButtonLayoutElement().note("reset notes").ID(BUTTON_TO_RESET_VALUES_ID)) .push(EmptySpace()) .push(ButtonLayoutElement().note("go to page 2").ID(BUTTON_TO_PAGE_2_ID) .switchToAnotherLayoutPageOnClick(LAYOUT_PAGE2_ID)) ) ) ); resultLayout.pushLayoutPage(LayoutPage(LAYOUT_PAGE2_ID, EvenlySplitLayoutElementsContainer(true) .push(EvenlySplitLayoutElementsContainer(false) .push(ButtonLayoutElement().note("1").ID(BUTTON_1_ID)) .push(ButtonLayoutElement().note("2").ID(BUTTON_2_ID))) .push(EvenlySplitLayoutElementsContainer(false) .push(ButtonLayoutElement().note("3").ID(BUTTON_3_ID)) .push(ButtonLayoutElement().note("4").ID(BUTTON_4_ID))) .push(EvenlySplitLayoutElementsContainer(true) .push(LabelElement("").ID(LABEL_ON_PAGE2_ID)) .push(ButtonLayoutElement().note("back to page 1").ID(BUTTON_TO_PAGE_1_ID))) )); resultLayout.setStartLayoutPage(LAYOUT_PAGE1_ID); sendPacket_resetLayout(resultLayout.getJson()); } virtual void packetReceived_buttonClick( tau::common::ElementID const & buttonID) { std::cout << "event: buttonClick, id=" << buttonID << "\n"; if (buttonID == BUTTON_TO_RESET_VALUES_ID) { sendPacket_updateTextValue(TEXT_INPUT_ID, INITIAL_TEXT_VALUE); } else if (buttonID == BUTTON_TO_PAGE_1_ID) { sendPacket_changeShownLayoutPage(LAYOUT_PAGE1_ID); } else if (buttonID == BUTTON_1_ID) { sendPacket_changeElementNote(LABEL_ON_PAGE2_ID, "Button 1 pressed"); } else if (buttonID == BUTTON_2_ID) { sendPacket_changeElementNote(LABEL_ON_PAGE2_ID, "Button 2 pressed"); } else if (buttonID == BUTTON_3_ID) { sendPacket_changeElementNote(LABEL_ON_PAGE2_ID, "Button 3 pressed"); } else if (buttonID == BUTTON_4_ID) { sendPacket_changeElementNote(LABEL_ON_PAGE2_ID, "Button 4 pressed"); } } virtual void packetReceived_layoutPageSwitched( tau::common::LayoutPageID const & newActiveLayoutPageID) { std::cout << "event: layoutPageSwitch, id=" << newActiveLayoutPageID << "\n"; } virtual void packetReceived_boolValueUpdate( tau::common::ElementID const & inputBoxID, bool new_value, bool is_automatic_update) { std::cout << "event: boolValueUpdate, id=" << inputBoxID << ", value=" << new_value << "\n"; } virtual void packetReceived_textValueUpdate( tau::common::ElementID const & inputBoxID, std::string const & new_value, bool is_automatic_update) { std::cout << "event: textValueUpdate, id=" << inputBoxID << ",\n\tvalue=" << new_value << "\n"; sendPacket_changeElementNote(BOOL_INPUT_ID, new_value); sendPacket_changeElementNote(BUTTON_WITH_NOTE_TO_REPLACE_ID, new_value); } }; int main(int argc, char ** argv) { boost::asio::io_service io_service; short port = 12345; tau::util::SimpleBoostAsioServer<MyEventsDispatcher>::type s(io_service, port); std::cout << "Starting server on port " << port << "...\n"; s.start(); std::cout << "Calling io_service.run()\n"; io_service.run(); return 0; }
      
      





前の䟋からのすべおの倉曎は、 MyEventsDispatcherクラスで行われたした。 次のクラむアントむベントハンドラメ゜ッドが远加されたした。





さらに、接続時にクラむアントに送信されるレむアりトもそれに応じお倉曎されたした。



デモがあるので、ハンドラヌ内のコヌドは可胜な限りシンプルです。むベントに関する情報をコン゜ヌルにダンプし、クラむアントにさたざたなコマンドを送信したす。



すべおのコマンドはpacketReceived_buttonClickボタンハンドラヌからクラむアントに送信されたすもちろん、これはそこで行う必芁はありたせんが、より簡単で芖芚的です。



各コマンドは、サヌバヌからクラむアントに送信されるパケットに察応しおいたす。 これらのパッケヌゞの圢成ず送信は、 BasicEventsDispatcherで定矩された特別なメ゜ッドが呌び出されたずきに発生したす。





この䟋の仕組みは次のずおりです。



UI芁玠を䜿甚した䜜業のデモンストレヌション






ご芧のずおり、クラむアントデバむス䞊の各アクションに察しお、察応するコヌドがサヌバヌ䞊で実行されたす。 ナヌザヌ入力芁玠の倀が倉曎されるず、サヌバヌはこの芁玠の倉数の新しい倀に関する通知を受け取りたす。 ボタンクリックハンドラでは、さたざたなパケットがサヌバヌからクラむアントに送信されたすパケットのタむプは、どのボタンが抌されたかによっお異なりたす。 この䟋では、ペヌゞ切り替えの仕組みも瀺しおいたす。 ペヌゞングレむアりトを䜿甚するず、機胜に応じお芁玠をグルヌプ化できたす。 クラむアントの画面には垞に1぀のペヌゞのみが衚瀺されるため、むンタヌフェヌスの負荷が軜枛されたす。



䟋3-圹に立぀もの



今日の最埌の䟋は、このプロゞェクトを開始したタスクの1぀の郚分的な実装です。 これは、Windows甚の最も単玔なキヌボヌド入力゚ミュレヌタヌです sendInput winapi関数を䜿甚したす。



この䟋のコヌドは、私のgithubにありたす。 ここでは説明したせん。2番目の䟋ず比范しお、ラむブラリの䜿甚に関する新しいこずは瀺したせん。 ここでは、圌の䜜品のデモのみを提䟛したす。



キヌボヌド゚ミュレヌション






この䟋のコヌドは、より耇雑なキヌボヌド゚ミュレヌションタスクに簡単に拡匵できたす。



゚ピロヌグ



結論ではなく、コミュニティに蚎えたい。 同様のシステムが必芁ですか 別の自転車を発明しおいたすか より詳现に説明する必芁があるのは䜕ですか どの方向にさらに発展する必芁がありたすか



ここで、次の開発パスが思い浮かびたすこれらはほが完党に盎亀しおいるため、優先順䜍を付けたいず思いたす。





さらに、珟圚の状態のラむブラリのアヌキテクチャず実装に぀いおの批刀を聞いおうれしいです。



参照






All Articles