开发实践中遇到的一些问题及答案(Android篇四)

文章介绍了如何在Android应用中使用ContentObserver监听Settings数据库字段变化,以及如何将byte数组转换为int,包括高位在前的转换方法。此外,还提到了AsyncTask在Android中的用法,用于在后台执行任务并在UI线程上更新结果。
摘要由CSDN通过智能技术生成

Some problems encountered and their answers in work (Android Chapter)


Android 应用A通过Settings.System.putInt(context.getContentResolver(), “FATIGURE_DRIVING_WARNING”, 0);//1代表正在播报疲劳驾驶 0 代表不在播报 , 默认值为0向Settings数据库写入相关数据,那么应用B应该如何监听字段为FATIGURE_DRIVING_WARNING的Settings数据库值变化
要监听设置数据库中  `FATIGURE_DRIVING_WARNING`  字段的变化,应用 B 可以注册  `ContentObserver`  来观察设置数据库的变化。可以使用上下文的  `ContentResolver`  方法向其注册  `ContentObserver` ,从而实现监听。
 以下是一个示例代码,演示如何注册一个  `ContentObserver`  来观察  `FATIGURE_DRIVING_WARNING`  字段在设置数据库中的变化:
ContentResolver resolver = context.getContentResolver();
 Uri uri = Settings.System.getUriFor("FATIGURE_DRIVING_WARNING");
 ContentObserver observer = new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        // 当 FATIGURE_DRIVING_WARNING 的值更改时,这个方法将被调用
    }
};
 resolver.registerContentObserver(uri, true, observer);
当  `FATIGURE_DRIVING_WARNING`  字段的值更改时, `ContentObserver`  的  `onChange()`  方法将被调用,应用 B 可以根据字段的新值执行任何必要的操作。
应用B可以通过注册ContentObserver来监听 Settings 数据库中 FATIGURE_DRIVING_WARNING 字段的变化。以下是示例代码:
// 创建 ContentResolver
ContentResolver resolver = getContentResolver();
 // 创建 ContentObserver
ContentObserver observer = new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange) {
        // FATIGURE_DRIVING_WARNING 字段的值发生变化,从数据库中获取最新的值
        int value = Settings.System.getInt(resolver, "FATIGURE_DRIVING_WARNING", 0);
        // 处理变化后的值
        handleFatigueDrivingWarning(value);
    }
};
 // 注册 ContentObserver
resolver.registerContentObserver(
        Settings.System.getUriFor("FATIGURE_DRIVING_WARNING"),
        true,
        observer
);
 // 处理变化后的值的方法
private void handleFatigueDrivingWarning(int value) {
    if (value == 1) {
        // 正在播报疲劳驾驶
    } else {
        // 不在播报
    }
}
在以上代码中,我们先创建一个 ContentResolver 对象 resolver,用于操作 Settings 数据库。然后创建一个 ContentObserver 对象 observer,重写其 onChange 方法,在字段 FATIGURE_DRIVING_WARNING 的值发生变化时获取最新的值并调用 handleFatigueDrivingWarning 方法处理变化后的值。接着,调用 resolver 的 registerContentObserver 方法注册 observer,指定要监听的字段为 FATIGURE_DRIVING_WARNING。最后,在 handleFatigueDrivingWarning 方法中根据 value 的值来处理疲劳驾驶警报的开启和关闭逻辑。

将byte数组转为int(高位在前,低位在后)

可以使用Java的 `ByteBuffer` 类将byte数组转换为int类型。以下是将byte数组转为int(高位在前,低位在后)Java代码示例:
byte[] byteArray = new byte[] {0x12, 0x34, 0x56, 0x78}; // 要转换的byte数组
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); // 将byte数组包装成ByteBuffer
byteBuffer.order(ByteOrder.BIG_ENDIAN); // 指定字节序为Big Endian,即高位在前,低位在后
int result = byteBuffer.getInt(); // 从ByteBuffer中获取int值
System.out.println(result); // 输出结果:305419896
在以上代码中,我们将byte数组  `byteArray`  包装成一个ByteBuffer对象  `byteBuffer` ,通过调用  `byteBuffer.order(ByteOrder.BIG_ENDIAN)`  指定字节序为Big Endian,然后从  `byteBuffer`  中获取int值  `result` 。由于我们指定了字节序为Big Endian,因此  `result`  中的高位在前,低位在后。
 需要注意的是,byte数组的长度必须为4,否则会抛出  `BufferUnderflowException`  异常。
另外一种将byte数组转为int(高位在前,低位在后)的方法是手动进行位运算。以下是示例代码:
byte[] byteArray = new byte[] {0x12, 0x34, 0x56, 0x78}; // 要转换的byte数组
int result = (byteArray[0] << 24) | ((byteArray[1] & 0xFF) << 16) | ((byteArray[2] & 0xFF) << 8) | (byteArray[3] & 0xFF);
System.out.println(result); // 输出结果:305419896
在以上代码中,我们将byte数组  `byteArray`  的每个元素通过位运算合成一个int值  `result` 。具体来说,我们使用左移运算符  `<<`  将  `byteArray[0]`  的值左移24位,也就是将其放在int值的最高位,然后使用位或运算符  `|`  将其与其它三个元素合成int值。在将  `byteArray[1]` ,  `byteArray[2]` ,  `byteArray[3]`  转为int值时,我们使用位与运算符  `&`  将其与  `0xFF`  进行位与运算,以确保它们的值在0~255的范围内。由于我们手动指定了位运算的顺序,因此  `result`  中的高位在前,低位在后。

用Kotlin实现将byte数组转为int(高位在前,低位在后)

同样可以使用Kotlin的  `ByteBuffer`  类来实现将byte数组转换为int类型。示例代码如下:
val byteArray = byteArrayOf(0x12, 0x34, 0x56, 0x78) // 要转换的byte数组
val byteBuffer = ByteBuffer.wrap(byteArray) // 将byte数组包装成ByteBuffer
byteBuffer.order(ByteOrder.BIG_ENDIAN) // 指定字节序为Big Endian,即高位在前,低位在后
val result = byteBuffer.int // 从ByteBuffer中获取int值
println(result) // 输出结果:305419896
在以上Kotlin代码中,我们与Java代码实现基本相同,只是使用了Kotlin的语法而已。需要注意的是,与Java不同的是,在Kotlin中不需要使用  `new`  关键字来创建ByteBuffer对象,可以直接调用  `ByteBuffer.wrap`  方法将byte数组包装成ByteBuffer对象。
 当然,Kotlin也支持手动进行位运算的方式实现将byte数组转换为int类型,与Java的实现方法类似,这里就不再赘述。

ping -c 3 192.170.8.99这个命令代表什么意思

以下对话的默认语言为英语,除非在下面指定语言名称。
 "ping -c 3 192.170.8.99" 这个命令的意思是测试本地主机与 IP 地址 192.170.8.99 之间的连通性。"-c 3" 参数指定 ping 命令应向目标 IP 地址发送三个数据包以测试其连通性。

AsyncTask<Integer,Void,Integer>用法

`AsyncTask`  是 Android 中的一个类,允许您执行后台操作并在 UI 线程上发布结果,而无需操纵线程和/或处理程序。该类具有三种通用类型:ParamsProgressResult。
以下是使用  `AsyncTask`  的示例代码:
private class MyTask extends AsyncTask<Integer, Void, Integer> {
     @Override
    protected Integer doInBackground(Integer... integers) {
        int number = integers[0];
        int count = 0;
        for (int i = 1; i <= number; i++) {
            if (number % i == 0) {
                count++;
            }
            try {
                Thread.sleep(500); // 模拟长时间运行的任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return count;
    }
     @Override
    protected void onPostExecute(Integer result) {
        Toast.makeText(getApplicationContext(), "Factors: " + result, Toast.LENGTH_LONG).show();
    }
}
在此示例中, `MyTask`  是一个私有内部类,它扩展了  `AsyncTask<Integer, Void, Integer>` 。第一个通用参数( `Integer` )是指将传递给  `doInBackground()`  方法的输入参数的类型。第二个通用参数( `Void` )是指使用  `publishProgress()`  方法报告的进度的类型,但由于在此示例中不使用进度报告,因此使用  `Void` 。
第三个通用参数( `Integer` )是指将由  `doInBackground()`  方法返回的结果的类型,并传递给  `onPostExecute()`  方法。
在  `doInBackground()`  方法中,我们通过计算整数的因数数量来模拟长时间运行的任务,并在每次迭代后睡眠500毫秒。此计算的结果作为  `Integer`  返回。
在  `onPostExecute()`  方法中,我们显示一个带有计算结果的 toast 消息。
要执行 `MyTask` ,我们可以调用:
MyTask task = new MyTask();
task.execute(12);
这将使用输入值为  `12`  执行  `doInBackground()`  方法,并将结果传递给  `onPostExecute()`  方法,后者将显示带有  `12`  的因数个数的 toast 消息。

List integerList = new List();此写法为何有错

List<Integer>  实际上是 Java 中的一个接口,这意味着您不能直接使用  new  关键字实例化它。如果您尝试这样做,将会得到一个编译时错误。相反,您需要使用  List  接口的一个具体实现,例如  ArrayListLinkedList 。 
以下是在 Java 中实例化一个  ArrayList  的示例:
List<Integer> integerList = new ArrayList<Integer>();
在这个示例中,我们正在创建一个  ArrayList  类的新实例,并将其赋值给  integerList  变量。 ArrayList  类实现了  List  接口,所以它可以在任何需要  List  的地方使用。 
不能使用  List<Integer> integerList = new List<Integer>();  的原因是  List  是一个接口,不能直接实例化。这将导致编译时错误。

修改一个文件所属群组

您可以使用chown命令将文件所有者更改为system。该命令的语法为:
chown [options] {new_owner}:{new_group} {file(s)}
其中,new_owner是要分配给文件的新所有者名称,new_group是要分配给文件的新组名称,file(s)是要更改用户和组的文件名列表。
例如,要将文件BtActive.INI的所有者更改为system,可以按以下方式执行命令:
sudo chown system:BtActive.INI
注意,您需要具有足够的权限才能更改文件的所有者和群组。

如何获取Android项目根目录下文件路径

您可以使用AndroidContext类来获取应用程序的根目录,然后使用JavaFile类来访问文件。以下是一个示例代码,用于获取根目录下名为"example.txt"的文件路径:
File file = new File(context.getFilesDir(), "example.txt");
String filePath = file.getAbsolutePath();
在这个示例中,context.getFilesDir()方法返回应用程序的内部存储目录,而"example.txt"是要访问的文件名。使用File类的getAbsolutePath()方法来获取文件的完整路径。
可以使用相对路径或绝对路径来访问项目根目录下的partNum.json文件。
 相对路径示例:
假设在readJsonTest的根目录下有一个名为partNum.json的文件,可以使用以下代码来获取此文件的输入流:
InputStream inputStream = Files.newInputStream(Paths.get("partNum.json"));
绝对路径示例:
可以使用绝对路径来获取partNum.json文件的输入流。假设partNum.json的绝对路径为"/data/user/0/com.example.readJsonTest/files/partNum.json",可以使用以下代码来获取此文件的输入流:
File file = new File("/data/user/0/com.example.readJsonTest/files/partNum.json");
InputStream inputStream = Files.newInputStream(file.toPath());
请注意,路径名称可能因项目的特定设置而有所不同,因此请根据实际情况进行调整。

Android 项目中如何获取根目录下文件的相对路径

可以使用 `Context` 对象来获取Android应用程序的根目录。在这个根目录中,可以使用相对路径来访问其他文件和目录。
以下是获取根目录下文件相对路径的示例代码:
Context context = getApplicationContext();
File file = new File(context.getFilesDir(), "filename.txt");
String path = file.getPath();
这里 `getFilesDir()` 方法返回一个 `File` 对象,它代表应用程序的内部存储目录。在这个目录中,可以直接使用文件名来访问文件。
 如果要访问根目录中的其他子目录或文件夹,则可以使用相对路径。例如,假设要访问根目录下的名为"data"的文件夹,可以使用以下代码:
File dataFolder = new File(context.getFilesDir(), "data");
请注意,如果要使用相对路径访问文件,请确保文件位于应用程序的内部存储目录或其子目录中。否则,您需要使用另一种方法来获取文件的路径,例如使用绝对路径或使用外部存储。

如何在Android 项目中保存各种外部文件

Android项目中保存各种外部文件,可以使用以下方法之一:
 1. 使用内部存储:
 内部存储是一种应用私有的文件存储空间,只允许应用本身访问。可以使用 `Context` 类提供的 `openFileOutput()` 和 `openFileInput()` 方法来创建和读取内部文件。例如,以下代码将创建一个名为“my_file”的文件,并将字符串“Hello world!”写入该文件:
String filename = "my_file";
String fileContents = "Hello world!";
try (FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}
要读取该文件,可以使用以下代码:
String filename = "my_file";
try (FileInputStream fis = openFileInput(filename)) {
    byte[] buffer = new byte[fis.available()];
    fis.read(buffer);
    String fileContents = new String(buffer);
} catch (IOException e) {
    e.printStackTrace();
}
2. 使用外部存储:
 外部存储是一种应用外部的文件存储空间,所有应用都可以访问。可以使用 `Environment.getExternalStorageDirectory()` 方法获取外部存储根目录,并使用 `File` 类的各种方法来创建、读取和写入文件。例如,以下代码将创建一个名为“my_file”的文件,并将字符串“Hello world!”写入该文件:
String filename = "my_file";
String fileContents = "Hello world!";
File file = new File(Environment.getExternalStorageDirectory(), filename);
try (FileOutputStream fos = new FileOutputStream(file)) {
    fos.write(fileContents.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}
要读取该文件,可以使用以下代码:
String filename = "my_file";
File file = new File(Environment.getExternalStorageDirectory(), filename);
try (FileInputStream fis = new FileInputStream(file)) {
    byte[] buffer = new byte[(int) file.length()];
    fis.read(buffer);
    String fileContents = new String(buffer);
} catch (IOException e) {
    e.printStackTrace();
}
请注意,使用外部存储需要声明适当的权限。在 `AndroidManifest.xml` 文件中添加以下行:
xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
此外,从Android 10开始,应用程序必须明确请求访问共享存储的权限,而不是访问整个外部存储。共享存储是一种特殊的外部存储,可以用于存储媒体文件和其他可以共享的文件。要访问共享存储,请使用 `MediaStore` 、 `StorageAccessFramework` 或使用以下代码之一请求访问权限:
ActivityResultLauncher<String[]> requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
    boolean granted = permissions.values().stream().allMatch(b -> b);
    if (granted) {
        // 授予了共享存储的访问权限
    } else {
        // 未授权共享存储的访问权限
    }
});
// 请求 MANAGE_EXTERNAL_STORAGE 权限,以便向用户请求共享存储的访问权限
ActivityResultLauncher<Intent> requestManageExternalStoragePermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
        // 向用户请求共享存储的访问权限
        String[] permissionsToRequest = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
        requestPermissionLauncher.launch(permissionsToRequest);
    } else {
        // 未授予 MANAGE_EXTERNAL_STORAGE 权限
    }
});
// 向用户请求 MANAGE_EXTERNAL_STORAGE 权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
    intent.setData(Uri.parse("package:" + getPackageName()));
    requestManageExternalStoragePermissionLauncher.launch(intent);
} else {
    // 授予了共享存储的访问权限
}
以上是在Android项目中保存各种外部文件的方法,可以根据实际需求选择适合自己的方法。

Android项目为什么不把外部文件放在根目录而是放在assets文件夹

Android项目中,assets文件夹是用于存储应用程序需要的原始资产文件的目录。这些文件可以是文本文件,字体文件,图像,音频,视频等。
 将外部文件放在assets文件夹中的好处是,这些文件可以打包成APK文件,同时也可以在安装后通过代码访问。与将文件放置在根目录下的相比,这样做可以使应用程序更容易管理和分发,并且可以进一步减小应用程序的大小。另外,放在根目录下的文件可能需要访问外部存储卡,这可能会增加应用程序的权限需求。
 但是,assets目录下的文件只能通过代码访问,不能像存储在外部存储卡上的文件一样直接访问。因此,如果需要在应用程序中读取或写入外部存储设备上的文件,则需要使用其他方法,例如使用Context.getExternalFilesDir()方法获取应用程序在外部存储设备上的特定文件夹的路径,并在该位置存储和读取文件。

Android 项目中外部文件必须放在assets文件夹里面吗,有其他替代的方法吗‘

Android项目中,您可以将外部文件放在其他位置,例如res/raw目录或磁盘上的任何位置,但是使用这些文件时需要使用不同的方法。以下是一些替代方法:
 1. Res/raw目录:将文件放在res/raw目录下,并使用以下代码读取文件:
InputStream is = getResources().openRawResource(R.raw.filename);
2. 磁盘上的任何位置:将文件放在磁盘上的任何位置,并使用以下代码读取文件:
File file = new File("/path/to/file");
InputStream is = new FileInputStream(file);
需要注意的是,在使用这些方法时,您需要获取正确的文件路径,并在读取文件时处理可能会发生的异常。此外,如果您需要在应用程序升级后更改文件,则需要考虑将文件放在外部存储设备上,并在运行时请求适当的权限来访问该设备。

RecycleView多种Item并滑动删除

首先,在 build.gradle 文件中添加以下依赖:
implementation 'androidx.recyclerview:recyclerview:1.2.1'

在布局文件中添加 RecyclerView<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
创建多种类型 Item 的数据模型类,并为每种类型创建对应的 ViewHolder 类,例如:
public class Message {
    private String text;
    private boolean isImportant;

    public Message(String text, boolean isImportant) {
        this.text = text;
        this.isImportant = isImportant;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public boolean isImportant() {
        return isImportant;
    }

    public void setImportant(boolean important) {
        isImportant = important;
    }
}

public class ImportantMessageViewHolder extends RecyclerView.ViewHolder {

    private TextView textView;

    public ImportantMessageViewHolder(@NonNull View itemView) {
        super(itemView);
        textView = itemView.findViewById(R.id.textView);
    }

    public void bind(Message message) {
        textView.setText(message.getText());
    }
}
创建 RecyclerView.Adapter 类,并重写以下方法:
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int VIEW_TYPE_NORMAL = 1;
    private static final int VIEW_TYPE_IMPORTANT = 2;

    private List<Message> messages;
    private OnMessageDeletedListener onMessageDeletedListener;

    public MessageAdapter(List<Message> messages, OnMessageDeletedListener onMessageDeletedListener) {
        this.messages = messages;
        this.onMessageDeletedListener = onMessageDeletedListener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_NORMAL) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.normal_message_item, parent, false);
            return new NormalMessageViewHolder(view);
        } else if (viewType == VIEW_TYPE_IMPORTANT) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.important_message_item, parent, false);
            return new ImportantMessageViewHolder(view);
        }
        throw new IllegalArgumentException("Invalid view type: " + viewType);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Message message = messages.get(position);
        if (holder instanceof NormalMessageViewHolder) {
            ((NormalMessageViewHolder) holder).bind(message);
        } else if (holder instanceof ImportantMessageViewHolder) {
            ((ImportantMessageViewHolder) holder).bind(message);
        }
    }

    @Override
    public int getItemCount() {
        return messages.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (messages.get(position).isImportant()) {
            return VIEW_TYPE_IMPORTANT;
        } else {
            return VIEW_TYPE_NORMAL;
        }
    }

    public void removeItem(int position) {
        messages.remove(position);
        notifyItemRemoved(position);
        onMessageDeletedListener.onMessageDeleted();
    }

    public interface OnMessageDeletedListener {
        void onMessageDeleted();
    }
}

这里我们将 RecyclerView.Adapter 泛型指定为 RecyclerView.ViewHolder,表示 ViewHolder 类型由 onCreateViewHolder() 方法动态创建。在 onCreateViewHolder() 方法中,根据不同的视图类型创建对应的 ViewHolder 实例,并返回。在 onBindViewHolder() 方法中,根据不同的 ViewHolder 类型绑定数据模型和视图。

在 ActivityFragment 中获取 RecyclerView 实例,并设置布局管理器、适配器和 ItemTouchHelperpublic class MainActivity extends AppCompatActivity implements MessageAdapter.OnMessageDeletedListener {

    private RecyclerView recyclerView;
    private List<Message> messages;
    private MessageAdapter messageAdapter;

    @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化数据
        messages = new ArrayList<>();
        messages.add(new Message("Hello", false));
        messages.add(new Message("World", true));
        messages.add(new Message("Android", false));
        messages.add(new Message("RecyclerView", true));

        // 获取 RecyclerView 实例
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // 创建 Adapter 实例,并设置 OnMessageDeletedListener 监听器
        messageAdapter = new MessageAdapter(messages, this);
        recyclerView.setAdapter(messageAdapter);

        // 创建 ItemTouchHelper 实例,并关联 RecyclerView
        ItemTouchHelper.SimpleCallback itemTouchHelperCallback =
                new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
                    @Override
                    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                        return false;
                    }

                    @Override
                    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                        int position = viewHolder.getAdapterPosition();
                        messageAdapter.removeItem(position);
                    }
                };
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }

    @Override
    public void onMessageDeleted() {
        // TODO: 处理消息删除事件
    }
}
在这段代码中,我们首先初始化了一个 Message 列表作为数据源。然后获取 RecyclerView 实例,并设置布局管理器、适配器和 ItemTouchHelper。

在 onSwiped() 方法中响应滑动删除操作,通过调用 messageAdapter.removeItem() 方法删除对应位置的数据,并更新视图。

在自定义 MessageAdapter 类中,我们为适配器提供了一个 removeItem() 方法,并在该方法中调用 notifyItemRemoved() 方法通知适配器数据发生变化。此外,我们还为适配器提供了一个 OnMessageDeletedListener 接口,用于监听消息删除事件,并在 ActivityFragment 中实现该接口处理事件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值