Android 低功耗蓝牙开发(扫描、连接)

简介: Android 低功耗蓝牙开发(扫描、连接)

Android 低功耗蓝牙开发(扫描、连接)


前言


之前我写过蓝牙开发的文章,只不过是针对于经典蓝牙,可以理解为普通蓝牙,连接的对象是经典蓝牙,列如手机蓝牙、蓝牙耳机等设备。而也有读者说在学习低功耗蓝牙,因此就有了这篇文章,一方面是为了丰富蓝牙的使用,一方面也是为了帮助看我文章的读者,我会讲的很细,很多人也说我在记流水账,不过这不重要,重要的是你从流水账里学到了什么。


正文


首先明白低功耗蓝牙是什么?


 蓝牙低能耗(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart)也称低功耗蓝牙,是蓝牙技术联盟设计和销售的一种个人局域网技术,旨在用于医疗保健、运动健身、信标、安防、家庭娱乐等领域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低功耗和成本。


效果图如下:


284322002f88106b0a1165ebba72246a.gif


概念已经了解了,下面创建一个名为BleDemo的项目来写这篇文章。


87fdde1e81853dcb6f364a8ebe44ba67.png


一、项目配置


 首先进行项目的配置,一个是build.gradle配置,一个是AndroidManifest.xml配置。

先进行项目的build.gradle的配置,添加jitpack仓库。


maven { url "https://jitpack.io"}

869e4a85257ad5bc19aa8e841ba1af63.png


再进行app的build.gradle的配置,这里需要添加几个依赖库,


//蓝牙扫描库
implementation 'no.nordicsemi.android.support.v18:scanner:1.5.0'
//权限请求 支持Androidx
implementation 'pub.devrel:easypermissions:3.0.0'
//让你的适配器一目了然,告别代码冗余
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'

66a6cc6de93d4275f801eff0893a8912.png


改完了build.gradle记得要Sync Now。


这个库是Nordic公司开发的,在蓝牙领域很出名的公司。这个版本是适配androidx的,一般现在创建新项目都是默认支持androidx的,不支持的话就说明你的AS该更新了。如果要支持support请到GitHub上去适配。其他的库或多或少都有接触过就不介绍了。


下面配置AndroidManifest.xml。


  <!-- 蓝牙权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 
    <!-- 支持ble的设备 -->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" /> 
    <!-- 定位权限 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


添加位置如下图所示


e3191695cd71fd380b8231e78b1a1801.png


 在Android 6.0以后的系统BLE scan需要申请location的相关权限才能支持BLE的一些功能,比如发现附近的beacons设备。


 这是开发的时候必须用到的权限,并非权限滥用。而在Android6.0以后则有了动态权限的申请,这里就说明一下等下为是什么要请求定位权限,后面就不要问我为什么扫描一个蓝牙还要打开定位权限这样的问题了。


二、权限请求


 这里主要是定位权限的请求,还有就是获得定位之后,蓝牙是否有打开也需要进行处理,下面进行具体的编码。


在MainActivity中新增一个方法,代码如下:


  /**
     * 检查Android版本
     */
    private void checkAndroidVersion() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //Android 6.0及以上动态请求权限
        } else {
            //检查蓝牙是否打开
        }
    }


这里进行Android版本的判断,6.0及以上则请求权限,6.0一下则判断蓝牙是否打开。

下面先写这个蓝牙是否打开的判断


  /**
     * 请求打开蓝牙
     */
    private static final int REQUEST_ENABLE_BLUETOOTH = 100;
    /**
     * 蓝牙适配器
     */
    private BluetoothAdapter bluetoothAdapter;
  /**
     * 是否打开蓝牙
     */
    public void openBluetooth() {
        //获取蓝牙适配器
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter != null) {//是否支持蓝牙
            if (bluetoothAdapter.isEnabled()) {//打开
                showMsg("蓝牙已打开");
            } else {//未打开
                startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BLUETOOTH);
            }
        } else {
            showMsg("你的设备不支持蓝牙");
        }
    }
    /**
     * Toast提示
     *
     * @param msg 内容
     */
    private void showMsg(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }


这里会有一个页面的返回结果,代码如下:


  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
                if (bluetoothAdapter.isEnabled()) {
                    //蓝牙已打开
                    showMsg("蓝牙已打开");
                } else {
                    showMsg("请打开蓝牙");
                }
            }
        }
    }


那么现在对于蓝牙是否打开的结果进行了处理,下面进行动态权限的请求。


  /**
     * 权限请求码
     */
    public static final int REQUEST_PERMISSION_CODE = 9527;
  /**
     * 请求权限
     */
    @AfterPermissionGranted(REQUEST_PERMISSION_CODE)
    private void requestPermission() {
        String[] perms = {Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION,};
        if (EasyPermissions.hasPermissions(this, perms)) {
            //权限通过之后检查有没有打开蓝牙
            openBluetooth();
        } else {
            // 没有权限
            EasyPermissions.requestPermissions(this, "App需要定位权限", REQUEST_PERMISSION_CODE, perms);
        }
    }


这里会检查权限,有权限检查有没有打开蓝牙,没有权限则请求权限,请求权限的结果代码如下:


  /**
     * 权限请求结果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 将结果转发给 EasyPermissions
    EasyPermissions.onRequestPermissionsResult(REQUEST_PERMISSION_CODE, permissions, grantResults, this);
    }


这个结果会通过@AfterPermissionGranted注解将结果返回给这个requestPermission方法,然后重新检查权限结果。下面只要在checkAndroidVersion中调用这个requestPermission()方法和openBluetooth()方法即可,如下图所示:


e569405bf962fd6816909057372777d2.png


现在就形成了一个逻辑链,不过还需要一个地方去调用这个checkAndroidVersion()方法,就直接在onCreate中调用吧。


fb2f619b1c8223aba75c9d6e0edcdb31.png

继续下一步。


三、扫描低功耗蓝牙


 扫描低功耗蓝牙,首先要有触发的地方,其次要有显示结果的地方,这些都需要进行UI的处理,那么下面进行布局的修改和增加,修改activity_main.xml,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <!--设备列表-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_device"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btn_start_scan"
        android:overScrollMode="never" />
    <!--开始扫描-->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_start_scan"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_above="@+id/btn_stop_scan"
        android:layout_margin="6dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="开始扫描" />
    <!--停止扫描-->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_stop_scan"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_margin="6dp"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="停止扫描" />
</RelativeLayout>


下面进行列表item的布局编写,在layout下新建一个item_device_rv.xml文件,文件代码如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:foreground="?attr/selectableItemBackground"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:padding="16dp">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_bluetooth_blue" />
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical"
            android:paddingStart="12dp">
            <TextView
                android:id="@+id/tv_device_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:singleLine="true"
                android:text="设备名称"
                android:textColor="@color/black"
                android:textSize="16sp" />
            <TextView
                android:id="@+id/tv_mac_address"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:ellipsize="end"
                android:singleLine="true"
                android:text="Mac地址" />
        </LinearLayout>
        <TextView
            android:id="@+id/tv_rssi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="信号强度" />
    </LinearLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#EEE" />
</LinearLayout>


这里面有一个图标,使用路径绘制的ic_bluetooth_blue.xml,放在drawable文件夹下,代码如下


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="36dp"
    android:height="36dp"
    android:autoMirrored="true"
    android:tint="#42A5F5"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M14.58,12.36l1.38,1.38c0.28,0.28 0.75,0.14 0.84,-0.24c0.12,-0.48 0.18,-0.99 0.18,-1.5c0,-0.51 -0.06,-1.01 -0.18,-1.48c-0.09,-0.38 -0.56,-0.52 -0.84,-0.24l-1.39,1.38C14.39,11.85 14.39,12.17 14.58,12.36zM18.72,7.51l-0.05,0.05c-0.25,0.25 -0.3,0.62 -0.16,0.94c0.47,1.07 0.73,2.25 0.73,3.49c0,1.24 -0.26,2.42 -0.73,3.49c-0.14,0.32 -0.09,0.69 0.16,0.94l0,0c0.41,0.41 1.1,0.29 1.35,-0.23c0.63,-1.3 0.98,-2.76 0.98,-4.3c-0.01,-1.48 -0.34,-2.89 -0.93,-4.16C19.83,7.22 19.13,7.1 18.72,7.51zM15,7l-4.79,-4.79C10.07,2.07 9.89,2 9.71,2h0C9.32,2 9,2.32 9,2.71v6.88L5.12,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0l0,0c-0.39,0.39 -0.39,1.02 0,1.41L8.59,12l-4.89,4.89c-0.39,0.39 -0.39,1.02 0,1.41h0c0.39,0.39 1.02,0.39 1.41,0L9,14.41v6.88C9,21.68 9.32,22 9.71,22h0c0.19,0 0.37,-0.07 0.5,-0.21L15,17c0.39,-0.39 0.39,-1.02 0,-1.42L11.41,12L15,8.42C15.39,8.03 15.39,7.39 15,7zM11,5.83l1.88,1.88L11,9.59V5.83zM12.88,16.29L11,18.17v-3.76L12.88,16.29z" />
</vector>


好了,现在针对于这个布局方面的内容告一段落,下面先运行一下了:


d01adcbe557668c9f9595d7d4dfebecf.gif


进行下一步操作。


先进行页面的初始化。新增一个initView的方法。


  private static final String TAG = MainActivity.class.getSimpleName();
  /**
     * nordic扫描回调
     */
    private ScanCallback scanCallback;
  /**
     * 初始化
     */
    private void initView() {
        RecyclerView rvDevice = findViewById(R.id.rv_device);
        findViewById(R.id.btn_start_scan).setOnClickListener(v -> startScanDevice());
        findViewById(R.id.btn_stop_scan).setOnClickListener(v -> stopScanDevice());
        //扫描结果回调
        scanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, @NonNull ScanResult result) {
                Log.d(TAG, "name:" + result.getDevice().getName() + ",rssi:" + result.getRssi());
            }
            @Override
            public void onScanFailed(int errorCode) {
                throw new RuntimeException("Scan error");
            }
        };
    }


这个initView主要是页面的初始化,列表在后面进行配置,根据扫描结果来定,然后就是配置扫描回调,这里注意导包的问题,不要到错了包。


ef571d0d770ce7672290bbfc59927781.png


然后还有一个开始扫描和停止扫描的方法。


  /**
     * 开始扫描设备
     */
    public void startScanDevice() {
        BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
        scanner.startScan(scanCallback);
    }
    /**
     * 停止扫描设备
     */
    public void stopScanDevice() {
        BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
        scanner.stopScan(scanCallback);
    }


下面在onCreate方法中调用initView()方法。


8d51a0fad4fdb03edf6b3b40a3ef7f04.png


下面就可以开始运行了。运行之后点击开始扫描按钮,就会扫描附近的低功耗蓝牙设备,(请在附近有已打开低功耗蓝牙时进行扫描)可以在日志栏处进行打印。


ab379ebd10599f23f57e2be34eef1e44.png


这里很明显,扫描到了一些蓝牙设备,并且很多设备没有设备名称。既然有了结果,那么下面就是将扫描到的结果显示在列表上,这样才更直观。


四、显示扫描设备


 下面将扫描结果渲染到列表上,首先明确列表要显示扫描设备的那些信息,从item来看有设备名、Mac地址、信号强度。那么可以根据这一个扫描的信息构建一个设备类,新建一个BleDevice类,代码如下:


package com.llw.bledemo.bean;
import android.bluetooth.BluetoothDevice;
/**
 * @author llw
 * @description BleDevice
 * @date 2021/7/21 19:20
 */
public class BleDevice {
    private BluetoothDevice device;
    private int rssi;
    private String realName;//真实名称
    /**
     * 构造Device
     * @param device 蓝牙设备
     * @param rssi 信号强度
     * @param realName 真实名称
     */
    public BleDevice(BluetoothDevice device, int rssi, String realName) {
        this.device = device;
        this.rssi = rssi;
        this.realName = realName;
    }
    public BluetoothDevice getDevice(){
        return device;
    }
    public int getRssi(){
        return rssi;
    }
    public void setRssi(int rssi) {
        this.rssi = rssi;
    }
    public String getRealName(){
        return realName;
    }
    public void setRealName(String realName) {
        this.realName = realName;
    }
    @Override
    public boolean equals(Object object) {
        if(object instanceof BleDevice){
            final BleDevice that =(BleDevice) object;
            return device.getAddress().equals(that.device.getAddress());
        }
        return super.equals(object);
    }
}


下面来写这个适配器,新建一个BleDeviceAdapter类,代码如下:


package com.llw.bledemo.adapter;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseViewHolder;
import com.llw.bledemo.R;
import com.llw.bledemo.bean.BleDevice;
import java.util.List;
/**
 * @author llw
 * @description BleDeviceAdapter
 * @date 2021/7/21 19:34
 */
public class BleDeviceAdapter extends BaseQuickAdapter<BleDevice, BaseViewHolder> {
    public BleDeviceAdapter(int layoutResId, List<BleDevice> data) {
        super(layoutResId, data);
    }
    @Override
    protected void convert(BaseViewHolder holder, BleDevice bleDevice) {
        holder.setText(R.id.tv_device_name, bleDevice.getRealName())
                .setText(R.id.tv_mac_address, bleDevice.getDevice().getAddress())
                .setText(R.id.tv_rssi, bleDevice.getRssi() + " dBm");
    }
}



下面回到MainActivity中对列表进行适配,先定义变量


  /**
     * 设备列表
     */
    private List<BleDevice> mList = new ArrayList<>();
    /**
     * 列表适配器
     */
    private BleDeviceAdapter deviceAdapter;


然后在initView方法中进行列表配置,代码如下:


  //列表配置
    deviceAdapter = new BleDeviceAdapter(R.layout.item_device_rv, mList);
    rvDevice.setLayoutManager(new LinearLayoutManager(this));
    //启用动画
    deviceAdapter.setAnimationEnable(true);
    //设置动画方式
    deviceAdapter.setAnimationWithDefault(BaseQuickAdapter.AnimationType.SlideInRight);
    rvDevice.setAdapter(deviceAdapter);


添加位置如下:


cd626048ff1be87a95e02944cefe8420.png


下面就是将扫描结果添加到列表中了,可以写一个方法addDeviceList(),代码如下:


  /**
     * 添加到设备列表
     *
     * @param bleDevice 蓝牙设备
     */
    private void addDeviceList(BleDevice bleDevice) {
        if (!mList.contains(bleDevice)) {
            bleDevice.setRealName(bleDevice.getRealName() == null ? "UNKNOWN" : bleDevice.getRealName());
            mList.add(bleDevice);
        } else {
            //更新设备信号强度值
            for (BleDevice device : mList) {
                device.setRssi(bleDevice.getRssi());
            }
        }
        //刷新列表适配器
        deviceAdapter.notifyDataSetChanged();
    }


然后在扫描的回调中进行调用即可。


5e27357f6449bda0c94dc9e164b260ed.png


点击开始的时候清理一下列表。


b7bbb23134aef7d26526aebea4badb11.png


下面运行一下:


69c3009a5dc1f66a13a8879b4573a8b5.gif

增加一个表示搜索的效果,在activity_main.xml中增加


  <androidx.core.widget.ContentLoadingProgressBar
        android:id="@+id/loading_progress_bar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        android:indeterminateTint="@color/purple_200"
        android:visibility="invisible"
        tools:ignore="UnusedAttribute"/>


然后这个进度条设置在列表的上面。


c148c22f60e08d6ce254a3446ee8cc68.png


回到MainActivity,创建变量:


  /**
     * 加载进度条
     */
    private ContentLoadingProgressBar loadingProgressBar;


绑定视图


e7b77d129ce86ff04fa80b18390d159f.png


控制视图


31366e30a8306c1ca184ffa5c07803ee.png


运行一下:


f9e437426f53bdf4f13df372eca7bd5c.gif



五、连接设备


 连接Ble设备其实也很简单,难的是连接之外的东西,先来构想一下连接功能的业务逻辑,点击设备列表中的设备,进行连接,先显示一个加载布局,表示现在正在连接,然后停止扫描,在根据设备的mac地址去连接这个设备,然后在连接设备的回调中处理连接设备的结果。嗯,就是这样。下面来编码,首先是加载布局的问题。在activity_main.xml中增加如下布局代码:


  <!--加载布局-->
    <LinearLayout
        android:id="@+id/lay_connecting_loading"
        android:layout_centerInParent="true"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:orientation="vertical"
        android:visibility="invisible"
        android:background="@color/white"
        android:gravity="center">
        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminate="true"
            android:indeterminateTint="@color/purple_200" />
        <TextView
            android:layout_marginTop="12dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="连接中..."
            android:textColor="@color/black"
            android:textSize="@dimen/sp_14" />
    </LinearLayout>


添加位置如下图


d12a20df15a2f7ab0e388d6efd4887f2.png


然后在MainActivity中创建变量


  /**
     * 等待连接
     */
    private LinearLayout layConnectingLoading;


绑定视图


4d64e30810435729d9aea8d426ecd5bf.png


下面新增一个方法,用来连接设备。在点击设备列表Item的时候调用。


  /**
     * 连接设备
     *
     * @param bleDevice 蓝牙设备
     */
    private void connectDevice(BleDevice bleDevice) {
        //显示连接等待布局
        layConnectingLoading.setVisibility(View.VISIBLE);
        //停止扫描
        stopScanDevice();
        //获取远程设备
        BluetoothDevice device = bleDevice.getDevice();
    //连接gatt
        device.connectGatt(this, false, new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                switch (newState) {
                    case BluetoothProfile.STATE_CONNECTED://连接成功
                        Log.d(TAG,"连接成功");
                        runOnUiThread(() -> {
                            layConnectingLoading.setVisibility(View.GONE);
                            showMsg("连接成功");
                        });
                        break;
                    case BluetoothProfile.STATE_DISCONNECTED://断开连接
                        Log.d(TAG,"断开连接");
                        runOnUiThread(() -> {
                            layConnectingLoading.setVisibility(View.GONE);
                            showMsg("断开连接");
                        });
                        break;
                    default:
                        break;
                }
            }
        });
    }


在initView()中设置列表点击。


  //item点击事件
    deviceAdapter.setOnItemClickListener((adapter, view, position) -> {
        //连接设备
        connectDevice(mList.get(position));
    });

6592ec7eb2d7579b5ab5a4fd0b21c271.png


OK,下面运行一下:


801ef3ed44fc38fdb7024fd6f365e67d.gif


这个布局背景是白色的不是很明显,改一下好了。在drawable文件夹下新建一个shape_loading_bg.xml,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/white" />
    <corners android:radius="20dp" />
    <stroke
        android:width="1dp"
        android:color="@color/purple_500" />
</shape>


然后设置到这个布局中


1c1c479e3277e6df7eccc5c4d179831c.png


运行看看


284322002f88106b0a1165ebba72246a.gif


嗯,还可以,就这样了。


有连接设备就自然有断开连接设备。再增加两个变量


  /**
     * Gatt
     */
    private BluetoothGatt bluetoothGatt;
    /**
     * 设备是否连接
     */
    private boolean isConnected = false;


修改connectDevice()中的代码,如下图所示


58a9b8d95a72e319ba90742282e966b0.png


再新建一个断开连接的方法,代码如下:


  /**
     * 断开设备连接
     */
    private void disconnectDevice() {
        if (isConnected && bluetoothGatt != null) {
            bluetoothGatt.disconnect();
        }
    }


这篇文章就到这里了,有问题的可以评论区留言或者私信我都行,山高水长,后会有期~

相关文章
|
2天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
4天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
4天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
5天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
15 2
|
6天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
6月前
|
存储 Java 开发工具
Android开发的技术与开发流程
Android开发的技术与开发流程
399 1
|
3月前
|
移动开发 搜索推荐 Android开发
安卓与iOS开发:一场跨平台的技术角逐
在移动开发的广阔舞台上,两大主角——安卓和iOS,持续上演着激烈的技术角逐。本文将深入浅出地探讨这两个平台的开发环境、工具和未来趋势,旨在为开发者揭示跨平台开发的秘密,同时激发读者对技术进步的思考和对未来的期待。
|
3月前
|
安全 Android开发 Swift
安卓与iOS开发:平台差异与技术选择
【8月更文挑战第26天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各占一方。本文旨在探索这两个系统在开发过程中的不同之处,并分析开发者如何根据项目需求选择合适的技术栈。通过深入浅出的对比,我们将揭示各自平台的优势与挑战,帮助开发者做出更明智的决策。
70 5
|
3月前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。
51 3
|
3月前
|
编解码 Android开发 iOS开发
安卓与iOS开发:平台差异下的技术创新之路
在数字时代的浪潮中,移动应用开发如同两股潮流——安卓与iOS,各自携带着独特的技术生态和文化基因。本文将深入探讨这两大平台的开发环境、编程语言和工具的差异,以及它们如何塑造了不同的用户体验和技术趋势。通过比较分析,我们旨在揭示跨平台开发的可能性和挑战,同时探索未来技术创新的方向。让我们一起跟随代码的足迹,穿越安卓的开放草原和iOS的精密园林,发现那些隐藏在平台差异之下的创新机遇。
41 1