Android——实现APP内算路导航

简介: 效果视频跳转至百度地图进行算路导航APP内进行算路导航功能实现跳转方式实现权限声明判断手机内是否安装百度地图实现跳转至百度地图非跳转方式实现UI设计背景圆点布局代码功能实现权限声明初始化BroadCast初始化车辆信息初始化节点信息节点信息交换地址信息转地理信息(经纬度)保存地理信息算路导航销毁


功能实现



跳转方式实现

功能描述:通过初始化百度地图API,获取当前位置的经纬度做为起点,所输入的地址做为目的地,然后封装成Uri格式,使用隐式Intent调用百度地图APP,最终实现算路导航。


权限声明

声明导航所需要具备的权限,例如:网路权限、位置权限等

private void InitPermission(){
        List<String> PermissionList = new ArrayList<>();
        //判断权限是否授权
        if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED) {
            PermissionList.add( Manifest.permission.ACCESS_FINE_LOCATION );
        }
        if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED) {
            PermissionList.add( Manifest.permission.READ_PHONE_STATE );
        }
        if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED) {
            PermissionList.add( Manifest.permission.WRITE_EXTERNAL_STORAGE );
        }
        if (!PermissionList.isEmpty()) {
            String[] Permissions = PermissionList.toArray( new String[PermissionList.size()] );//转化为数组
            ActivityCompat.requestPermissions( Function.this, Permissions, 1 );//一次性申请权限
        } else {
            /*****************如果权限都已经声明,开始配置参数*****************/
            requestLocation();
        }
    }


判断手机内是否安装百度地图


百度地图包名

 public static final String BAIDUMAPPACKAGE = "com.baidu.BaiduMap"; // 百度地图包名

通过以百度地图包名为索引,查找手机内是否存在该应用,并返回boolean值

 public static boolean isBaiduMapInstalled(){
        return isInstallPackage(BAIDUMAPPACKAGE);
    }
    private static boolean isInstallPackage(String packageName) {
        return new File("/data/data/" + packageName).exists();
    }


实现跳转至百度地图

以获取自身的经纬度为导航起点,以输入的地址做为导航终点。最后通过Intent隐式跳转,唤起百度地图APP

 public  void  Navigation(String EndAddress){
 Uri uri = Uri.parse( "baidumap://map/direction?origin=" + Latitude + "," + Longitude + "&" + "destination=" + EndAddress + "&mode=driving&package=com.baidu.BaiduMap;end" );
 startActivity( new Intent( Intent.ACTION_VIEW, uri ) );
    }


非跳转方式实现


功能描述:首先通过将输入的地址转为经纬度形式,然后通过百度地图导航API进行算路节点计算,然后完成导航规划


UI设计

image.png

背景

image.png

在drawable目录下创建一个文件,并实现一个圆角矩形

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
 <solid android:color="#ffffff"/>
    <corners android:radius="10dp"/>
</shape>

圆点

image.png

在drawable目录下创建一个文件,并实现一个实心圆

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#4CAF50"/>
    <size android:height="10dp" android:width="10dp"/>

布局代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Activity.SelectNodeActivity"
    android:orientation="vertical"
    android:background="#F1EDED">
    <include layout="@layout/titlebar"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:background="@drawable/white_radiu_bg">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/grey_radiu_bg"
            android:layout_margin="20dp"
            android:padding="10dp">
        <LinearLayout
            android:layout_width="0dp"
            android:layout_weight="7"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:padding="@dimen/navi_dimens_5dp">
                <View
                    android:layout_width="10dp"
                    android:layout_height="10dp"
                    android:background="@drawable/green_point"
                    android:layout_gravity="center"
                    android:layout_marginRight="5dp"/>
                <EditText
                    android:id="@+id/startAddress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:hint="please enter starting point"
                    android:textColor="#000000"
                    android:textSize="15sp"
                    android:layout_gravity="center"
                    android:layout_marginLeft="5dp"
                    android:background="@null"/>
            </LinearLayout>
            <!--分割线-->
            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#DBCACA"
                android:layout_marginTop="5dp"
                android:layout_marginBottom="5dp"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:padding="@dimen/navi_dimens_5dp">
                <View
                    android:layout_width="10dp"
                    android:layout_height="10dp"
                    android:background="@drawable/red_point"
                    android:layout_gravity="center"
                    android:layout_marginRight="5dp"/>
                <EditText
                    android:id="@+id/endAddress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:hint="please enter ending point"
                    android:textColor="#000000"
                    android:textSize="15sp"
                    android:layout_marginLeft="5dp"
                    android:background="@null"
                    android:layout_gravity="center"/>
            </LinearLayout>
        </LinearLayout>
            <LinearLayout
                android:id="@+id/exchangeStr"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_gravity="center"
                android:layout_marginLeft="10dp"
                android:onClick="ExchangeStr">
                <ImageView
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:src="@drawable/icon_exchange"
                    android:layout_gravity="center"/>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/white_radiu_bg"
        android:layout_margin="10dp"
        android:padding="10dp"
        android:layout_alignParentBottom="true">
        <Button
            android:id="@+id/exeProcess"
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:background="@drawable/green_radiu_bg"
            android:text="Start Navigation"
            android:textAllCaps="false"
            android:textColor="#ffffff"
            android:layout_margin="10dp"
            android:onClick="ExeProcess"/>
    </LinearLayout>
    </RelativeLayout>
</LinearLayout>


功能实现

在介绍功能前,首先先介绍两个概念,即地理编码与反地理编码

地理编码:地址信息转换为地理坐标(经纬度)
反地理编码:地理坐标(经纬度)转换为地址信息


权限声明

将所需要声明的权限列为一个数组

private static final String[] authBaseArr = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

遍历数组,判断权限是否声明,并返回boolean值做为判断标识符

 private void InitPermission(
        // 申请权限
        if (Build.VERSION.SDK_INT >= 23) {
            if (!hasBasePhoneAuth()) {
                requestPermissions(authBaseArr, authBaseRequestCode);
            }
        }
    }
    private boolean hasBasePhoneAuth() {
        PackageManager pm = this.getPackageManager();
        for (String auth : authBaseArr) {
            if (pm.checkPermission(auth, this.getPackageName()) != PackageManager
                    .PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }


初始化BroadCast

  private void InitBroadCastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.navi.ready");
        mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                BNDemoFactory.getInstance().initCarInfo();
                BNDemoFactory.getInstance().initRoutePlanNode();
            }
        };
        registerReceiver(mReceiver, filter);
    }

初始化车辆信息

若没有手动修改车辆信息,则以如下信息为基础

 public void initCarInfo() {
        // 驾车车牌设置
        // BaiduNaviManagerFactory.getCommonSettingManager().setCarNum("京A88888");
        // 货车信息
        BNTruckInfo truckInfo = new BNTruckInfo.Builder()
                .plate("京A88888")
                .axlesNumber(2)
                .axlesWeight(1)
                .emissionLimit(VehicleConstant.EmissionStandard.S3)
                .length(5)
                .weight(2)
                .loadWeight(1)
                .oilCost("40000")
                .plateType(VehicleConstant.PlateType.BLUE)
                .powerType(VehicleConstant.PowerType.OIL)
                .truckType(VehicleConstant.TruckType.HEAVY)
                .height(2)
                .width(2.5f)
                .build();
        // 该接口会做本地持久化,在应用中设置一次即可
        BaiduNaviManagerFactory.getCommonSettingManager().setTruckInfo(truckInfo);
        // 摩托车信息
        BNMotorInfo motorInfo = new BNMotorInfo.Builder()
                .plate("京A88888")
                .plateType(VehicleConstant.PlateType.BLUE)
                .motorType(VehicleConstant.MotorType.OIL)
                .displacement("")
                .build();
        // 该接口会做本地持久化,在应用中设置一次即可
        BaiduNaviManagerFactory.getCommonSettingManager().setMotorInfo(motorInfo);
        // BaiduNaviManagerFactory.getCommonSettingManager().setTestEnvironment(false);
        BaiduNaviManagerFactory.getCommonSettingManager().setNodeClick(true);
    }

初始化节点信息

若没有手动修改路径节点信息,则以如下信息为基础

  public void initRoutePlanNode() {
        startNode = new BNRoutePlanNode.Builder()
                .latitude(40.041690)
                .longitude(116.306333)
                .name("百度大厦")
                .description("百度大厦")
                .build();
        endNode = new BNRoutePlanNode.Builder()
                .latitude(39.908560)
                .longitude(116.397609)
                .name("北京天安门")
                .description("北京天安门")
                .build();
    }


节点信息交换

即将起点地址信息与终点地址信息完成互换,利用一个临时变量存储一方数据,完成互换

public void ExchangeStr(View view) {
        String Start = startAddress.getText().toString().trim();
        String End = endAddress.getText().toString().trim();
        startAddress.setText( End );
        endAddress.setText( Start );
    }


地址信息转地理信息(经纬度)

Geocoder为百度地图提供的一个地址信息与地理信息相互转换的一个API

geocoder = new Geocoder( SelectNodeActivity.this );

通过用户键入的地址信息,由getFromLocationName()方法进行转换,并返回一个Address对象;需要注意的是键入的地址信息应当较为精准,例如xxx省xxx市xxx区xxx地,这样所转换的地理信息,精度才不会偏移太大

private String GetNodeValue(String Address){
        builder = new StringBuilder(  );
        try {
            List<android.location.Address> addresses = geocoder.getFromLocationName(Address,1 );
            double start_latitude = addresses.get( 0 ).getLatitude();//纬度
            double start_longitude = addresses.get( 0 ).getLongitude();//经度
            builder.append( start_longitude ).append( "," ).append( start_latitude );
        } catch (IOException e) {
            Toast.makeText( SelectNodeActivity.this,"error",Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
        return builder.toString();
    }


保存地理信息

通过使用一个简单工厂单例类保存所转换的地理信息(经纬度)

private void AddrToCoordinate(){
        boolean flag = Geocoder.isPresent();
        if (!flag)
            Toast.makeText( SelectNodeActivity.this,"error",Toast.LENGTH_SHORT ).show();
        else {
            String start = GetNodeValue(startAddress.getText().toString().trim());
            String end = GetNodeValue(endAddress.getText().toString().trim());
            //TODO 设置起点经纬度
            if (!TextUtils.isEmpty(start)) {
                BNDemoFactory.getInstance().setStartNode(this, start);
                Log.d( "start:",start );
            }
            //TODO 设置终点经纬度
            if (!TextUtils.isEmpty(end)) {
                BNDemoFactory.getInstance().setEndNode(this, end);
                Log.d( "end:",end );
            }
        }
    }


算路导航

 private void routePlanToNavi(final Bundle bundle) {
        List<BNRoutePlanNode> list = new ArrayList<>();
        //TODO 在主函数中获取经纬度,开始算路导航
        list.add(BNDemoFactory.getInstance().getStartNode(this));
        list.add(BNDemoFactory.getInstance().getEndNode(this));
        // 关闭电子狗
        if (BaiduNaviManagerFactory.getCruiserManager().isCruiserStarted()) {
            BaiduNaviManagerFactory.getCruiserManager().stopCruise();
        }
        BaiduNaviManagerFactory.getRoutePlanManager().routePlanToNavi(
                list,
                IBNRoutePlanManager.RoutePlanPreference.ROUTE_PLAN_PREFERENCE_DEFAULT,
                bundle, handler);
    }

节点计算需要耗不少的内存空间以及时间,故使用异步通信,不影响主线程的分发以及UI的刷新

private Handler handler = new Handler( Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_START:
                    Toast.makeText(SelectNodeActivity.this, "算路开始", Toast.LENGTH_SHORT).show();
                    ControlBoardWindow.getInstance().showControl("算路开始");
                    break;
                case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_SUCCESS:
                    Toast.makeText(SelectNodeActivity.this, "算路成功", Toast.LENGTH_SHORT).show();
                    ControlBoardWindow.getInstance().showControl("算路成功");
                    // 躲避限行消息
                    Bundle infoBundle = (Bundle) msg.obj;
                    if (infoBundle != null) {
                        String info = infoBundle
                                .getString( BNaviCommonParams.BNRouteInfoKey.TRAFFIC_LIMIT_INFO);
                        Log.e("OnSdkDemo", "info = " + info);
                    }
                    break;
                case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_FAILED:
                    ControlBoardWindow.getInstance().showControl("算路失败");
                    Toast.makeText(SelectNodeActivity.this.getApplicationContext(),
                            "算路失败", Toast.LENGTH_SHORT).show();
                    break;
                case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_TO_NAVI:
                    Toast.makeText(SelectNodeActivity.this.getApplicationContext(),
                            "算路成功准备进入导航", Toast.LENGTH_SHORT).show();
                    ControlBoardWindow.getInstance().showControl("算路成功准备进入导航");
                    switch (mPageType) {
                        case BNDemoUtils.NORMAL:
                            BNDemoUtils.gotoNavi(SelectNodeActivity.this);
                            break;
                        case BNDemoUtils.ANALOG:
                            BNDemoUtils.gotoAnalog(SelectNodeActivity.this);
                            break;
                        case BNDemoUtils.EXTGPS:
                            BNDemoUtils.gotoExtGps(SelectNodeActivity.this);
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
    };


销毁

当使用完成之后,应该将可销毁的内容放入onDestroy内,避免内存空间的浪费

  @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
        stopService(new Intent(this, ForegroundService.class));
    }
相关文章
|
2天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
22 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
2天前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
24 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
14天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
115 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
12天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
35 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
4月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
306 0
安卓项目:app注册/登录界面设计
|
1月前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
36 1
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
2月前
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
87 11
|
4月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
575 0
|
5月前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
|
存储 缓存 安全
Android14 适配之——现有 App 安装到 Android14 手机上需要注意些什么?
Android14 适配之——现有 App 安装到 Android14 手机上需要注意些什么?
568 0

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 3
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 4
    MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
  • 5
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 6
    微信小程序 app.json 配置文件解析与应用
  • 7
    【Azure App Service】基于Linux创建的App Service是否可以主动升级内置的Nginx版本呢?
  • 8
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 9
    【Azure Function】Function App出现System.IO.FileNotFoundException异常
  • 10
    原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力