• 鸿蒙软总线跨设备访问该怎么玩——小总结


    目录:

    1、跨设备启动FA、跨设备迁移、回迁

    2、跨设备连接Service

    3、更多文章

    鸿蒙软总线跨设备访问该怎么玩——小总结

     重点撸代码:


    1、跨设备启动FA、跨设备迁移、回迁

    (1)权限

    ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE:用于允许监听分布式组网内的设备状态变化。
    ohos.permission.GET_DISTRIBUTED_DEVICE_INFO:用于允许获取分布式组网内的设备列表和设备信息。
    ohos.permission.GET_BUNDLE_INFO:用于查询其他应用的信息。
    ohos.permission.DISTRIBUTED_DATASYNC:用于允许不同设备间的数据交换。
    
    "reqPermissions": [
        {"name": "ohos.permission.DISTRIBUTED_DATASYNC"},
        {"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"}, 
        {"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO" }, 
       {"name": "ohos.permission.GET_BUNDLE_INFO"} 
    ]
    
    //主动申明,要多设备协同,让用户选择允许还是禁止
    requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);

    (2)界面:ability_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <DirectionalLayout
        xmlns:ohos="http://schemas.huawei.com/res/ohos"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:orientation="vertical">
    
        <Button
            ohos:id="$+id:main_start_fa_btn"
            ohos:height="match_content"
            ohos:width="300vp"
            ohos:text="1.启动远程设备的FA"
            ohos:text_size="20fp"
            ohos:text_color="#ffffff"
            ohos:background_element="$graphic:button_bg"
            ohos:layout_alignment="horizontal_center"
            ohos:top_padding="8vp"
            ohos:bottom_padding="8vp"
            ohos:left_padding="40vp"
            ohos:right_padding="40vp"
            ohos:top_margin="20vp"
            />
    
        <Button
            ohos:id="$+id:main_migration_btn"
            ohos:height="match_content"
            ohos:width="300vp"
            ohos:text="2.迁移到远程设备"
            ohos:text_size="20fp"
            ohos:text_color="#ffffff"
            ohos:background_element="$graphic:button_bg"
            ohos:layout_alignment="horizontal_center"
            ohos:top_padding="8vp"
            ohos:bottom_padding="8vp"
            ohos:left_padding="40vp"
            ohos:right_padding="40vp"
            ohos:top_margin="20vp"
            />
        
    </DirectionalLayout>

    button_bg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape  xmlns:ohos="http://schemas.huawei.com/res/ohos"
            ohos:shape="rectangle">
        <solid ohos:color="#007DFF"/>
        <corners ohos:radius="40"/>
    </shape>

    另外我们需要的Page Abiltiy:MigrationAbility、RemoveAbility
     
    MainAbilitySlice:

    public class MainAbilitySlice extends AbilitySlice {
       private Button mainStartFABtn,mainMigrationBtn;
    
        @Override
        public void onStart(Intent intent) {
            super.onStart(intent);
            super.setUIContent(ResourceTable.Layout_ability_main);
            mainStartFABtn = (Button)findComponentById(ResourceTable.Id_main_start_fa_btn);
            mainMigrationBtn = (Button)findComponentById(ResourceTable.Id_main_migration_btn);
            
            mainStartFABtn.setClickedListener(mClickListener);
            mainMigrationBtn.setClickedListener(mClickListener);
        }
    
        private Component.ClickedListener mClickListener = new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                int compoentId = component.getId();
                switch (compoentId){
                    case ResourceTable.Id_main_start_fa_btn:
                        //点击后跨设备打开Fa
                        //第一种写法
                        Intent intent = new Intent();
                        Operation op = new Intent.OperationBuilder()
                                .withDeviceId(Common.getOnLineDeviceId())
                                .withBundleName("com.ybzy.demo")
                                .withAbilityName("com.ybzy.demo.RemoveAbility")
                                .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
                                .build();
                        intent.setOperation(op);
                        intent.setParam("msg","我夸设备把你这个FA拉起来了!");
                        startAbility(intent);
    
                        //第二钟写法
                        intent.setElement(new ElementName(Common.getOnLineDeviceId()
                                                ,"com.ybzy.demo","com.ybzy.demo.RemoveAbility"));
                        intent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
                        intent.setParam("msg","我夸设备把你这个FA拉起来了!");
                        startAbility(intent);
    
                        break;
                    case ResourceTable.Id_main_migration_btn:
                        //点击后进入要迁移的Ability页面
                        Intent migrationIntent = new Intent();
                        migrationIntent.setElement(new ElementName("","com.ybzy.demo"
                                                        ,"com.ybzy.demo.MigrationAbility"));
                        startAbility(migrationIntent);
                        break;
                    default:
                        break;
                }
            }
        };
    
        @Override
        public void onActive() {
            super.onActive();
        }
    
        @Override
        public void onForeground(Intent intent) {
            super.onForeground(intent);
        }
    }

    ability_migration.xml

    <?xml version="1.0" encoding="utf-8"?>
    <DirectionalLayout
        xmlns:ohos="http://schemas.huawei.com/res/ohos"
        ohos:height="match_parent"
        ohos:width="match_parent"
        ohos:background_element="#00ffff"
        ohos:orientation="vertical">
    
        <Text
            ohos:id="$+id:migration_text"
            ohos:height="match_content"
            ohos:width="250vp"
            ohos:background_element="#0088bb"
            ohos:layout_alignment="horizontal_center"
            ohos:text="下面是一个可编辑的文本框"
            ohos:text_size="50"
            ohos:padding="5vp"
            ohos:top_margin="30vp"
            />
    
        <TextField
            ohos:id="$+id:migration_textfield"
            ohos:height="250vp"
            ohos:width="250vp"
            ohos:hint="请输入..."
            ohos:layout_alignment="horizontal_center"
            ohos:background_element="#ffffff"
            ohos:text_color="#888888"
            ohos:text_size="20fp"
            ohos:padding="5vp"
            />
        <Button
            ohos:id="$+id:migration_migration_btn"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="点击迁移"
            ohos:text_size="20fp"
            ohos:text_color="#ffffff"
            ohos:background_element="$graphic:button_bg"
            ohos:top_padding="8vp"
            ohos:bottom_padding="8vp"
            ohos:left_padding="50vp"
            ohos:right_padding="50vp"
            ohos:layout_alignment="horizontal_center"
            ohos:top_margin="30vp"
            />
    
        <Button
            ohos:id="$+id:migration_migration_back_btn"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="点击迁移回来"
            ohos:text_size="20fp"
            ohos:text_color="#ffffff"
            ohos:background_element="$graphic:button_bg"
            ohos:top_padding="8vp"
            ohos:bottom_padding="8vp"
            ohos:left_padding="50vp"
            ohos:right_padding="50vp"
            ohos:layout_alignment="horizontal_center"
            ohos:top_margin="30vp"
            />
    </DirectionalLayout>

    RemoveAbility...把接收到的值显示到页面就行,setText()

    (3)工具类

    public class Common{
        public static String getDeviceId(){
            List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
            if(deviceList.isEmpty()){
                return null;
            }
            int deviceNum = deviceList.size();
            List<String> deviceIds = new ArrayList<>(deviceNum);
            List<String> deviceNames = new ArrayList<>(deviceNum);
            deviceList.forEach((device)->{
                deviceIds.add(device.getDeviceId());
                deviceNames.add(device.getDeviceName());
            });
    
            //我们这里的实验环境,就两部手机,组件还没讲
            //我就直接使用deviceIds的第一个元素,做为启动远程设备的目标id
            String devcieIdStr = deviceIds.get(0);
            return devcieIdStr;
        }
    
    
        public static void myShowTip(Context context,String msg){
            //提示框的核心组件文本
            Text text = new Text(context);
            text.setWidth(MATCH_CONTENT);
            text.setHeight(MATCH_CONTENT);
            text.setTextSize(16, Text.TextSizeType.FP);
            text.setText(msg);
            text.setPadding(30,20,30,20);
            text.setMultipleLine(true);
            text.setMarginLeft(30);
            text.setMarginRight(30);
            text.setTextColor(Color.WHITE);
            text.setTextAlignment(TextAlignment.CENTER);
        
            //给上面的文本设置一个背景样式
            ShapeElement style = new ShapeElement();
            style.setShape(ShapeElement.RECTANGLE);
            style.setRgbColor(new RgbColor(77,77,77));
            style.setCornerRadius(15);
            text.setBackground(style);
        
            //构建存放上面的text的布局
            DirectionalLayout mainLayout = new DirectionalLayout(context);
            mainLayout.setWidth(MATCH_PARENT);
            mainLayout.setHeight(MATCH_CONTENT);
            mainLayout.setAlignment(LayoutAlignment.CENTER);
            mainLayout.addComponent(text);
        
            //最后要让上面的组件绑定dialog
            ToastDialog toastDialog = new ToastDialog(context);
            toastDialog.setSize(MATCH_PARENT,MATCH_CONTENT);
            toastDialog.setDuration(1500);
            toastDialog.setAutoClosable(true);
            toastDialog.setTransparent(true);
            toastDialog.setAlignment(LayoutAlignment.CENTER);
            toastDialog.setComponent((Component) mainLayout);
            toastDialog.show();
        }
        
    }

    (4)实现功能
    MigrationAbilitySlice:

    public class MigrationAbilitySlice extends AbilitySlice implements IAbilityContinuation {
        TextField migrationTextField;
        Button migrationMigrationBtn,migrationMigrationBackBtn;
        String msg = "";
    
        @Override
        public void onStart(Intent intent) {
            super.onStart(intent);
            super.setUIContent(ResourceTable.Layout_ability_migration);
    
            migrationTextField = (TextField)findComponentById(ResourceTable.Id_migration_textfield);
            migrationTextField.setText(msg);
            migrationMigrationBtn = (Button)findComponentById(ResourceTable.Id_migration_migration_btn);
            migrationMigrationBtn.setClickedListener(component -> {
                //1、要进行迁移的Ability实现接口 IAbilityContinuation,实现的方法返回值改成true
                //2、要进行迁移的Ability下面关联的所有AbilitySlice都要实现接口 IAbilityContinuation,
                //  实现的方法上处理数据
                //3、进行迁移
                String deviceId = Common.getOnLineDeviceId();
                if(deviceId != null){
    //                continueAbility(deviceId); 
                    continueAbilityReversibly(deviceId);
                }
            });
    
            migrationMigrationBackBtn = (Button) findComponentById(ResourceTable
                                                                        .Id_migration_migration_back_btn);
            migrationMigrationBackBtn.setClickedListener(component -> {
                reverseContinueAbility();
            });
        }
    
        @Override
        public void onActive() {
            super.onActive();
        }
    
        @Override
        public void onForeground(Intent intent) {
            super.onForeground(intent);
        }
    
        @Override
        public boolean onStartContinuation() {
            return true;
        }
    
        @Override
        public boolean onSaveData(IntentParams intentParams) {
            intentParams.setParam("msg",migrationTextField.getText());
            return true;
        }
    
        @Override
        public boolean onRestoreData(IntentParams intentParams) {
            msg = intentParams.getParam("msg").toString();
    //        getUITaskDispatcher().asyncDispatch(() -> {
    //            migrationTextField.setText(intentParams.getParam("msg").toString());
    //        });
            return true;
        }
    
        @Override
        public void onCompleteContinuation(int i) {
    
        }
    
    }

    2、跨设备连接Service

    启动远程设备Service的代码示例如下:
    添加按钮:

    <Button
        ohos:id="$+id:main_start_removeService_btn"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:text="远程启动ServiceAbility"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        ohos:background_element="$graphic:button_bg"
        ohos:layout_alignment="horizontal_center"
        ohos:top_padding="8vp"
        ohos:bottom_padding="8vp"
        ohos:left_padding="40vp"
        ohos:right_padding="40vp"
        ohos:top_margin="20vp"
        />
        
        <Button
        ohos:id="$+id:main_stop_removeService_btn"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:text="远程关闭ServiceAbility"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        ohos:background_element="$graphic:button_bg"
        ohos:layout_alignment="horizontal_center"
        ohos:top_padding="8vp"
        ohos:bottom_padding="8vp"
        ohos:left_padding="40vp"
        ohos:right_padding="40vp"
        ohos:top_margin="20vp"
        />

    新建RemoteServiceAbility:

    public class RemoteServiceAbility extends Ability {
        private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
        private Source sVideoSource;
        private Player sPlayer;
    
        @Override
        public void onStart(Intent intent) {
            HiLog.error(LABEL_LOG, "RmoteServiceAbility::onStart");
            super.onStart(intent);
            Common.myShowTip(this,"remote onstart");
            sPlayer = new Player(RemoteServiceAbility.this);
            new PlayerThread().start();
        }
        class PlayerThread extends Thread {
            @Override
            public void run() {
                try {
                    File mp3FilePath = getExternalFilesDir(Environment.DIRECTORY_MUSIC);
                    if (!mp3FilePath.exists()) {
                        mp3FilePath.mkdirs();
                    }
                    File mp3File = new File(mp3FilePath.getAbsolutePath() + "/" + "bj.mp3");
    
                    Resource res = getResourceManager()
                            .getRawFileEntry("resources/rawfile/bj.mp3").openRawFile();
                    byte[] buf = new byte[4096];
                    int count = 0;
                    FileOutputStream fos = new FileOutputStream(mp3File);
                    while ((count = res.read(buf)) != -1) {
                        fos.write(buf, 0, count);
                    }
                    FileDescriptor fileDescriptor = new FileInputStream(mp3File).getFD();
                    sVideoSource = new Source(fileDescriptor);
                    sPlayer.setSource(sVideoSource);
                    sPlayer.prepare();
                    sPlayer.setVolume(0.3f);
                    sPlayer.enableSingleLooping(true);
                    sPlayer.play();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void onStop() {
            super.onStop();
            Common.myShowTip(RemoteServiceAbility.this,"remote onStop");
            sPlayer.stop();
        }
    
        @Override
        public IRemoteObject onConnect(Intent intent) {
            return null;
        }
        @Override
        public void onDisconnect(Intent intent) {
        }
    }

    启动:

    case ResourceTable.Id_main_start_remoteService_btn:
    
        Common.myShowTip(MainAbilitySlice.this,deviceId);
        Intent startRemoteServiceIntent = new Intent();
        startRemoteServiceIntent.setElement(new ElementName(
                deviceId,
                "com.ybzy.demo",
                "com.ybzy.demo.RemoteServiceAbility"
        ));
        startRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
        startAbility(startRemoteServiceIntent);
        break;
     

    关闭远程设备Service:

    case ResourceTable.Id_main_stop_remoteService_btn:
        Common.myShowTip(MainAbilitySlice.this,deviceId);
        Intent stopRemoteServiceIntent = new Intent();
        stopRemoteServiceIntent.setElement(new ElementName(
                deviceId,
                "com.ybzy.demo",
                "com.ybzy.demo.RemoteServiceAbility"
        ));
        stopRemoteServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
        stopAbility(stopRemoteServiceIntent);
        break;

    仅通过启动和停止Service Ability两种方式对Service进行调度无法应对需长期交互的场景,
    简单地说,信息就是只能去,回不来!
    因此,分布式任务调度平台向开发者提供了跨设备Service连接及断开连接的能力。
    链接上了,信息可去可回!
     
    链接是使用connectAbility()方法,需要传入目标Service的Intent与接口IAbilityConnection的实例对象。
     
    接口IAbilityConnection提供了两个方法供开发者实现:
    (1)onAbilityConnectDone()用来处理连接的回调。
    (2)onAbilityDisconnectDone()用来处理断开连接的回调。
     
    我们可以在onAbilityConnectDone()中获取管理链接的代理,进一步为了使用该代理跨设备调度Service,
    开发者需要在本地及远端分别实现对外接口一致的代理,这个接口是IRemoteBroker。
     
    添加按钮:

    <Button
        ohos:id="$+id:main_connect_remoteService_btn"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:text="远程链接ServiceAbility"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        ohos:background_element="$graphic:button_bg"
        ohos:layout_alignment="horizontal_center"
        ohos:top_padding="8vp"
        ohos:bottom_padding="8vp"
        ohos:left_padding="40vp"
        ohos:right_padding="40vp"
        ohos:top_margin="20vp"
        />
    
    <Button
        ohos:id="$+id:main_use_remoteService_btn"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:text="使用远程ServiceAbility"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        ohos:background_element="$graphic:button_bg"
        ohos:layout_alignment="horizontal_center"
        ohos:top_padding="8vp"
        ohos:bottom_padding="8vp"
        ohos:left_padding="40vp"
        ohos:right_padding="40vp"
        ohos:top_margin="20vp"
        />
    
    <Button
        ohos:id="$+id:main_disconnect_remoteService_btn"
        ohos:height="match_content"
        ohos:width="300vp"
        ohos:text="远程断开ServiceAbility"
        ohos:text_size="20fp"
        ohos:text_color="#ffffff"
        ohos:background_element="$graphic:button_bg"
        ohos:layout_alignment="horizontal_center"
        ohos:top_padding="8vp"
        ohos:bottom_padding="8vp"
        ohos:left_padding="40vp"
        ohos:right_padding="40vp"
        ohos:top_margin="20vp"
        />

    发起连接的本地侧的代理示例如下:

    public class MyRemoteProxy implements IRemoteBroker {
        //IRemoteBroker:获取远程代理对象的持有者
        private static final int ERR_OK = 0;
        //COMMAND_PLUS表示有效消息进行通信的约定的标记,MIN_TRANSACTION_ID是这个标记可以用的最小值:1
        private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;
    
        //IRemoteObject:此接口
        // 可用于查询或获取接口描述符、
        // 添加或删除死亡通知、
        // 将对象状态转储到特定文件以及发送消息。
        private final IRemoteObject remote;
        public MyRemoteProxy(IRemoteObject remote) {
            this.remote = remote;
        }
    
        @Override
        public IRemoteObject asObject() {//获取远程代理对象的方法
            return remote;
        }
    
        public int plus(int a,int b) throws RemoteException {
            //MessageParcel:这个类提供了读写对象、接口标记、文件描述符和大数据的方法。
            MessageParcel data = MessageParcel.obtain();
            //obtain()创建索引为0的空MessageParcel对象
            MessageParcel reply = MessageParcel.obtain();
    
            //MessageOption:定义与sendRequest一起发送消息的选项。
            // option不同的取值,决定采用同步或异步方式跨设备调用Service
            // 这个例子我们需要同步获取对端Service执行加法运算后的结果,同步模式调用sendRequest接口,即MessageOption.TF_SYNC
            // 对应的是异步:TF_ASYNC
            MessageOption option = new MessageOption(MessageOption.TF_SYNC);
            data.writeInt(a);
            data.writeInt(b);
    
            try {
                remote.sendRequest(COMMAND_PLUS, data, reply, option);
                //第1个参数:约定通信双方确定的消息标记。
                //第2个参数:发送到对等端侧的数据包裹MessageParcel对象。
                //第3个参数:对等端侧返回的数据包裹MessageParcel对象。
                //第4个参数:设置发送消息,用同步还是异步模式。
                int ec = reply.readInt(); //返回通信成不成功,约定的标记ERR_OK
                if (ec != ERR_OK) {
                    throw new RemoteException();
                }
                int result = reply.readInt();
                return result;
            } catch (RemoteException e) {
                throw new RemoteException();
            } finally {
                data.reclaim(); //reclaim()清除不再使用的MessageParcel对象。
                reply.reclaim();
            }
        }
    }

    等待连接的远端侧的代理示例如下:

    public class MyRemote extends RemoteObject implements IRemoteBroker{
        private static final int ERR_OK = 0;
        private static final int ERROR = -1;
        private static final int COMMAND_PLUS = IRemoteObject.MIN_TRANSACTION_ID;
     
        public MyRemote() {
            super("MyService_Remote");
        }
     
        @Override
        public IRemoteObject asObject() {
            return this;
        }
     
        @Override
        public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
            if (code != COMMAND_PLUS) {
                reply.writeInt(ERROR);
                return false;
            }
            int value1 = data.readInt();
            int value2 = data.readInt();
            int sum = value1 + value2;
            reply.writeInt(ERR_OK);
            reply.writeInt(sum);
            return true;
        }
    }

    等待连接侧还需要作如下修改:

    // 绑定前面定义的代理,实例化出发起链接侧需要的代理
    private MyRemote remote = new MyRemote();
    @Override
    public IRemoteObject onConnect(Intent intent) {
        //链接成功的时候,给发起链接侧返回去
        return remote.asObject();
    }

    完成上述步骤后,可以通过点击事件实现连接、利用连接关系控制PA以及断开连接等行为,代码示例如下:

    private MyRemoteProxy mProxy = null;
    // 创建连接回调实例
    private IAbilityConnection conn = new IAbilityConnection() {
        // 连接到Service的回调
        @Override
        public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {
            // 在这里开发者可以拿到服务端传过来IRemoteObject对象,从中解析出服务端传过来的信息
            mProxy = new MyRemoteProxy(iRemoteObject);
            UIUtils.showTip(MainAbilitySlice.this,"拿到remoteObject:" + mProxy);
        }
    
        // 意外断开连接才会回调
        @Override
        public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {
        }
    };
    
    
    
    // 连接远程
    case ResourceTable.Id_main_connect_remoteService_btn:
        //1、实现连接的本地侧的代理
        //2、实现等待连接的远端侧的代理
        //3、修改等待连接侧的Service
        //4、在本地(发起链接侧)获取远端(被链接侧)返回过来的链接代理,创建链接后的回调函数
        //5、实现链接,通过代理对象使用Service的服务
        if (deviceId != null) {
            Intent connectServiceIntent = new Intent();
            connectServiceIntent.setElement(new ElementName(
                    deviceId,
                    "com.ybzy.demo",
                    "com.ybzy.demo.RemoteServiceAbility"
            ));
            connectServiceIntent.setFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE);
            connectAbility(connectServiceIntent, iAbilityConnection);
        }
        break;
        
        
    // 链接后使用
    case ResourceTable.Id_main_use_remoteService_btn:
        if (mProxy != null) {
            int ret = -1;
            try {
                ret = mProxy.plus(10, 20);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Common.myShowTip(MainAbilitySlice.this, "获取的结果:" + ret);
        }
        break;
        
    // 用完断开
    case ResourceTable.Id_main_disconnect_remoteService_btn:
        disconnectAbility(iAbilityConnection);
        break;

    作者:zhonghongfa

    想了解更多内容,请访问51CTO和华为合作共建的鸿蒙社区:https://harmonyos.51cto.com

  • 相关阅读:
    Smarty模板引擎技术(三)
    Smarty模板引擎技术(二)
    Smarty模板引擎技术(一)
    Ajax技术
    JavaScript--XML DOM
    JavaScript--HTML DOM
    [转]常用正则表达式
    JavaScript--事件
    CentOS 下开启PHP错误提示
    JavaScript实例
  • 原文地址:https://www.cnblogs.com/HarmonyOS/p/14577967.html
Copyright © 2020-2023  润新知