V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
commoccoom
V2EX  ›  Java

应该是一个 Android 开发中的新手问题,请教一下

  •  
  •   commoccoom · 2022-02-16 21:30:37 +08:00 · 1827 次点击
    这是一个创建于 793 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    在之前的帖子中,我想实现在 PC 端(也就是网页上)控制大疆无人机 https://www.v2ex.com/t/823520

    在和大疆的客服沟通后,发现只能通过手机 APP 接遥控器的方式控制无人机

    然后在网上找到了一个 NanoHTTPD 的库,心想我只要在网页上实现无人机的起飞降落和获取图传就可以了,那么是不是在 APP 里实现一个小型的 Web Server ,然后发送 Post 请求到手机 APP 是不是就可以了呢?

    然后开始自学 Java 和 Android ,买了一台最便宜的大疆无人机 Mini SE

    终于参照大疆的 Mobile SDK ,做了一个可以起飞降落和获取图传的 APP 。

    心想接下去就是把 NanoHTTPD 库跟 APP 结合就可以了。

    难题

    public class MainActivity extends AppCompatActivity {
    
        private App myApp;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button startButton = (Button) findViewById(R.id.start_httpd);
            Button stopButton = (Button) findViewById(R.id.stop_httpd);
            Button take_off = (Button) findViewById(R.id.take_off);
            Button landing = (Button) findViewById(R.id.landing);
    
            startButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    try{
                        myApp = new App(this); 
                        Log.e("onClick", "WebServer started");
    
                    }catch (IOException e){
                        e.printStackTrace();
                        Log.e("onClick", "WebServer start failed" + e.getMessage());
                    }
                }
            });
    
            stopButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if(myApp != null)
                    {
                        myApp.closeAllConnections();
                        myApp = null;
                        Log.e("onClick", "Web server close");
                    }
                }
            });
        }
    }
    
    class App extends NanoHTTPD{
    
        private String commandName;
        private String operator;
    
        public String getCommandName()
        {
            return commandName;
        }
    
        public String getOperator()
        {
            return operator;
        }
    
        public App(View.OnClickListener onClickListener) throws IOException {
            super(8080);
            start(NanoHTTPD.SOCKET_READ_TIMEOUT,false);
        }
    
        public void starting() throws IOException {
            new App((View.OnClickListener) this);
        }
    
        private void requestBodyProcess(Map<String, String> map,String requestBody)
        {
            String requestBodyRegex = "\\w+=\\w+&\\w+=\\w+";
    
            boolean isRequestBodyMatch = Pattern.matches(requestBodyRegex, requestBody);
            if(isRequestBodyMatch)
            {
                Pattern command = Pattern.compile("(?<=\\bcommandName=)\\w+");
                Matcher command_matcher = command.matcher(requestBody);
                if(command_matcher.find())
                {
                    map.put("commandName", command_matcher.group(0));
                }
    
                command = Pattern.compile("(?<=\\boperator=)\\w+");
                command_matcher = command.matcher(requestBody);
                if(command_matcher.find())
                {
                    map.put("operator", command_matcher.group(0));
                }
            }
            else
            {
                map = null;
            }
        }
    
        @Override
        public Response serve(IHTTPSession session) {
            Map<String, String> files = new HashMap<String, String>();        
            Map<String, String> map = new HashMap<String, String>();
            // HTTP GET
            if (session.getMethod() == Method.GET) {
                String itemIdRequestParameter = session.getParameters().get("itemId").get(0);
                return newFixedLengthResponse("Requested itemId = " + itemIdRequestParameter);
            }
            // HTTP POST
            if (session.getMethod() == Method.POST) {
                try {
                    session.parseBody(files);
                    String requestBody = session.getQueryParameterString();
                    requestBodyProcess(map,requestBody);
                    commandName = map.get("commandName");
                    operator = map.get("operator");
                    return newFixedLengthResponse("Command Name = " + map.get("commandName") + "\n" + "Operator = "
                            + map.get("operator") );
    
                } catch (IOException | ResponseException e) {
                    // handle
                }
            }
            return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT,
                    "The requested resource does not exist");
        }
    }
    

    我在 APP 类里实现了获取 Post 发送过来的是起飞还是降落commandName,但是我在MainActivity根本不知道myApp这个对象在哪里了,startButtononClick执行后,myApp被创建了,但是之后去了哪里?我又能在哪里再次操作它呢。。。

    我怎么才能在获取到起飞的 Post 请求后,调用起飞按钮呢?

    请教一下我应该去补充什么知识呢?感谢了!

    第 1 条附言  ·  2022-02-24 16:33:59 +08:00

    又看了几天书,大致明白了接口的使用方法。用接口改写了。 创建一个飞行控制接口,定义起飞降落

    interface FlyController {
        void takeOff();
        void landing();
    }
    

    MainActivity 实现接口 FlyController 重写两个方法

    public class MainActivity extends AppCompatActivity implements FlyController {
        ....
        
        @Override
        public void takeOff() {
            Log.d("appinfo","Take Off");
        }
    
        @Override
        public void landing() {
            Log.d("appinfo","Landing");
        }    
    
    }
    

    NanoHTTPD接收 FlyController 接口,后面调用接口的方法

    class App extends NanoHTTPD {
    
        private FlyController f;
    
        public App(FlyController f) throws IOException {
            super(8080);
            this.f = f;
            start(NanoHTTPD.SOCKET_READ_TIMEOUT,false);
        }
    
        ....
    

    myApp实例化的时候传入 MainActivity 类

    myApp = new App(MainActivity.this);
    
    13 条回复    2022-02-24 16:27:01 +08:00
    noahhhh
        1
    noahhhh  
       2022-02-16 21:56:40 +08:00 via Android
    关注的有在大疆 Android 开发的,你可以试试问他 http://weibo.com/u/1624312593
    commoccoom
        2
    commoccoom  
    OP
       2022-02-16 22:08:00 +08:00 via iPad
    @noahhhh 这,直接微博私信,是不是有点太突兀了。另外,我现在能用 app 起飞,降落了,只是不知道怎么把 post 发来的参数和起飞按钮对应起来。
    r00tt
        3
    r00tt  
       2022-02-16 22:24:51 +08:00
    问:myApp 被创建了,但是之后去了哪里?我又能在哪里再次操作它呢?
    答:在 MainActivity 中已经赋值给 myApp 了,可以通过`this.myApp`来进一步访问


    问:我怎么才能在获取到起飞的 Post 请求后,调用起飞按钮呢?
    答:应该不需要调用起飞按钮,直接在收到起飞的 post 请求后,调用 DJI 的 sdk 起飞。

    ---

    NanoHTTPD 实现的 App 可以直接放到一个 Service 里面,在后台运行,UI 与 Service 通过 AIDL 通讯
    commoccoom
        4
    commoccoom  
    OP
       2022-02-17 10:12:38 +08:00
    @r00tt this.myApp 提示 on a null object reference
    CharmingCheung
        5
    CharmingCheung  
       2022-02-17 13:26:31 +08:00
    额,楼主是不是没有编程基础。。或者说没有 Java 编程基础
    CharmingCheung
        6
    CharmingCheung  
       2022-02-17 13:29:17 +08:00
    App 类构造函数里传入 OnClickListener 是什么作用。。还有 staring 函数里的 this 就是 App 本身啊,怎么能强转成 OnClickListener 。。
    CharmingCheung
        7
    CharmingCheung  
       2022-02-17 13:39:59 +08:00
    @commoccoom 根据 MainActivity 里的代码得出,myApp 的实例化是在 startButton 点击之后才发生的,所以你要不就把实例化挪到 onClickListener 外面,也就是 onCreate 层里,要不就是先点击了 startButton ,再去拿 this.myApp 。
    另外,this.myApp 在这里的严格意义是 MainActivity.this.myApp ,你如果搞不懂 this 用法,最好先这样写
    commoccoom
        8
    commoccoom  
    OP
       2022-02-17 14:32:06 +08:00
    @CharmingCheung 首先,感谢回复,是的,没什么 Java 基础,就是春节期间看了一下《 Java 核心技术》这本书,然后还有《第一行代码 Android 》。

    另外,经过了一早上的搜索和思考,问题已经解决了。

    把 Class App 定义为 MainActivity 的内部类,然后获取 Post 参数后,调用外部类 MainActivity.this.customizeFunction 的方法。

    再次感谢!两位的帮助 @r00tt @CharmingCheung
    commoccoom
        9
    commoccoom  
    OP
       2022-02-17 14:33:53 +08:00
    @CharmingCheung

    还有 staring 函数里的 this 就是 App 本身啊,怎么能强转成 OnClickListener 。。

    这是报错了之后,我点了 iDEA 自动给我修复的,因为能够运行,所以我也没细究是什么意思😂
    CharmingCheung
        10
    CharmingCheung  
       2022-02-17 14:47:28 +08:00
    @commoccoom 解决了就行

    但还是提醒一下,如果求稳定的话,如#3 所说,还是要把 App 和 DJI SDK 的调用逻辑都挪到 Service 里运行,并且是 Foreground Service ,甚至是子进程的 Service ,通过 Activity 里的按钮触发逻辑,与 Service 通信来控制服务的开关。
    除非你只是临时使用几分钟,否则 MainActivity 被销毁了,就接收不到 post 过来的指令了,控制无人机这种东西还是稳点好。
    commoccoom
        11
    commoccoom  
    OP
       2022-02-17 14:50:39 +08:00
    @CharmingCheung 好的,感谢建议。慢慢摸索了。
    yawenimy122
        12
    yawenimy122  
       2022-02-17 15:05:29 +08:00
    啊这,
    没有基础的,很难给你说明白欸,但是总体来说,逻辑不通, 代码运行起来报错了,只是 try catch 了, 所以没闪退, 这也是为什那么 myApp 是空的原因, App 的构建方法出问题了, 分清楚 View.OnClickedListener 和 Activity, this 的用法和代表的意思
    commoccoom
        13
    commoccoom  
    OP
       2022-02-24 16:27:01 +08:00
    @yawenimy122 又看了几天 java 的书,搞明白了接口怎么用了,把代码改成可以使用接口的了。感谢回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2899 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 02:50 · PVG 10:50 · LAX 19:50 · JFK 22:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.