第一行代码——第八章:丰富你的程序——运用手机多媒体

目录:

8.1 将程序运行到手机上

8.2 使用通知

8.2.1 通知的基本用法

8.2.2 通知的进阶技巧

8.2.3 通知的高级功能

8.3 调用摄像头和相册

8.3.1 调用摄像头拍照

8.3.2 从相册中选择照片

8.4 播放多媒体文件

8.4.1 播放音频

8.4.2 播放视频

8.5 小结与点评


知识点:

8.1 将程序运行到手机上

打开手机开发者模式-USB 调试 (有些手机还有通过USB安装应用程序) 

在设置中找不到开发者模式的话 需要点击系统版本 打开开发者

若仍然在AS中找不到手机,可能驱动未安装成功

本人解决方法:使用360手机助手 搞定的。

8.2 使用通知

8.2.1 通知的基本用法

通知的用法灵活,既可以在活动里创建,也可在广播接收器里创建,还可在服务里创建。

Intent intent = new Intent(this, OtherNoticeActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//        manager.cancel(1);  退出指定通知
        //channelId  渠道id
        Notification notification = new NotificationCompat.Builder(this,"chat")
                .setContentTitle("This is content title")
                .setContentText("this is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setAutoCancel(true)
                .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
                //<uses-permission android:name="android.permission.VIBRATE" />
                .setVibrate(new long[]{0, 1000, 1000, 1000})//设置静止震动时长  震动1秒 静止1秒 在震动1秒
                .setLights(Color.GREEN, 1000, 1000)//灯光
//                .setDefaults(NotificationCompat.DEFAULT_ALL)//根据当前情况来决定播放什么铃声,以及如何震动
                .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.mipmap.jelly_fish)))
                .setPriority(NotificationCompat.PRIORITY_HIGH)//显示级别  横幅
                .setContentIntent(pendingIntent)
//                .setFullScreenIntent(pendingIntent,true)//响应紧急事件 比如来电 直接跳转
                .build();

        manager.notify(1,notification);

8.2.2 通知的进阶技巧

上面提到了通知的基本用法,接下来介绍一些通知的其他技巧,比如:

在通知发出时播放一段音频,调用 setSound() 方法:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) // 在音频目录下选个音频文件
        .build();

在通知到来时让手机振动,设置 vibrate 属性:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setVibrate(new long[]{0,1000,1000,1000}) // 数组下标0表静止的时长,下标1表振动的时长,下标2表静止的时长,以此类推
        .build();

当然别忘了声明振动权限:

<uses-permission android:name="android.permission.VIBRATE" />

在通知到来时显式手机 LED 灯,调用 setLights() 方法:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setLights(Color.GREEN,1000,1000) // 三个参数:LED 灯的颜色、灯亮时长、灯暗时长
        .build();

当然也可直接使用默认效果,如下:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setDefaults(NotificationCompat.DEFAULT_ALL)
        .build();

8.2.3 通知的高级功能

NotificationCompat.Builder 类中的 setStyle() 方法

setStyle() 方法接收一个 NotificationCompat.style 参数,这个参数用来构造具体的富文本信息,如长文字、图片等。

https://segmentfault.com/a/1190000008241257

8.3 调用摄像头和相册

8.3.1 调用摄像头拍照

8.3.2 从相册中选择照片

调用摄像头和相册 是非常常用的功能,在这里,我们不仅实现了掉用摄像头和相册,同时也对于Android 7.0 系统进行了适配,主要的一点就是 获取Uri 相关连的代码,在返回结果的时候也是需要我们对于Uri进行解析,请看代码:

package com.dak.administrator.firstcode.multi_media;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;

public class CameraAlbumActivity extends AppCompatActivity {

    private ImageView picture;
    private static final int TAKE_PHOTO = 1;
    private static final int CHOOSE_PHOTO = 2;
    private Uri imageUri;

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

        picture = findViewById(R.id.picture);

        Button takePhoto = findViewById(R.id.take_photo);
        takePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                openCamera();
            }
        });

        Button fromAblum = findViewById(R.id.choose_from_album);
        fromAblum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (ContextCompat.checkSelfPermission(CameraAlbumActivity.this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(CameraAlbumActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                } else {
                    openAlbum();
                }

            }
        });
    }


    /**
     * 打开相机
     */
    private void openCamera() {

        //创建用处存储拍照后的图片
        File outputImage = new File(getExternalCacheDir(), "output_image.jpg");

        try {
            if (outputImage.exists()) {
                outputImage.delete();
            }
            outputImage.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (Build.VERSION.SDK_INT >= 24) {
            imageUri = FileProvider.getUriForFile(CameraAlbumActivity.this,
                    "com.dak.administrator.firstcode.fileprovider", outputImage);
        } else {
            imageUri = Uri.fromFile(outputImage);
        }
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//输出地址
        startActivityForResult(intent, TAKE_PHOTO);
    }

    /**
     * 打开相册
     * gallery
     */
    private void openAlbum() {
        //很漂亮页面
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent, CHOOSE_PHOTO);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openAlbum();
                } else {
                    Toast.makeText(this, "请获取权限", Toast.LENGTH_SHORT).show();
                }
                break;
        }

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
//                        压缩 循环 太慢
//                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//                        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
//                        int options = 100;
//                        while (baos.toByteArray().length / 1024 > 100) {//循环判断如果压缩后图片是否大于100kb,大于继续压缩
//                            baos.reset();//重置baos即清空baos
//                            bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
//                            options -= 10;//每次都减少10
//                        }
//                        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
//                        Bitmap comBitmap = BitmapFactory.decodeStream(bais);
                        picture.setImageBitmap(bitmap);

                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    if (Build.VERSION.SDK_INT >= 19) {
                        //4.4及以上系统使用这个方法处理图片
                        handleImageOnKitKat(data);
                    } else {
                        //4.4及以下系统使用这个方法处理图片
                        handleImageBeforeKitKat(data);
                    }
                }
                break;
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void handleImageOnKitKat(Intent data) {
        String imagePath = null;
        Uri uri = data.getData();

        if (DocumentsContract.isDocumentUri(this, uri)) {
            //如果是document 类型的uri ,则通过document id处理

            String docId = DocumentsContract.getDocumentId(uri);
            if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
                //如果uri的authority是media格式 再次解析id 将字符串分割取出后半部分才能得到真正的数字id
                String id = docId.split(":")[1];//解析出数字格式的id
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                imagePath = getImagePath(contentUri, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            //如果是content类型的uri,则使用普通方式处理
            imagePath = getImagePath(uri, null);
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
            //如果是file类型的uri,直接获取图片路径
            imagePath = uri.getPath();
        }
        displayImage(imagePath);//根据图片路径显示图片
    }

    private String getImagePath(Uri externalContentUri, String selection) {
        String path = null;
        //通过Uri和selection来获取真实图片路径
        Cursor cursor = getContentResolver().query(externalContentUri, null, selection, null, null);
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }

    private void displayImage(String imagePath) {
        if (imagePath != null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            picture.setImageBitmap(bitmap);
        } else {
            Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
        }

    }

    private void handleImageBeforeKitKat(Intent data) {
        Uri uri = data.getData();
        String imagePath = getImagePath(uri, null);
        displayImage(imagePath);
    }


}

Xml页面:

<?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"
    android:orientation="vertical"
    tools:context="com.dak.administrator.firstcode.multi_media.CameraAlbumActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Take Photo"
        android:id="@+id/take_photo"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="打开相册"
        android:id="@+id/choose_from_album"
        />
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/picture"
        />



</LinearLayout>

在AndroidManifest.xml中配置Provider

 <!-- authorities 和FileProvider.getUriForFile第二个参数一致 -->
        <!-- name 使用自定义类 或者android.support.v4.content.FileProvider -->
        <!-- grantUriPermissions  允许你有给其赋予临时访问权限的权力 -->
        <provider
            android:name=".provider"
            android:authorities="com.dak.administrator.firstcode.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">

            <!-- 指定uri共享路径   name 固定 -->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <paths>
        <!--指定Uri共享   name 随便填 path标识共享的具体路径 空值代表整个sd卡   也可以仅共享 某张图片路径
        path="images"
        -->

        <external-path
            name="my_images"
            path="" />

    </paths>
</resources>

当然也别忘了声明访问SD卡的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

注意事项:在实际开发中最好根据项目的需求先对照片进行适当的压缩,然后再加载到内存中。

8.4 播放多媒体文件

  在安卓中播放音频文件一般用 MediaPlayer 类来实现,播放视频文件主要用 VideoView 类来实现。

MediaPlayer 常用方法:

MediaPlayer构造方法
create创建一个要播放的多媒体
getCurrentPosition得到当前播放位置
getDuration得到文件的时间
getVideoHeight得到视频的高度
getVideoWidth得到视频的宽度
isLooping是否循环播放
isPlaying是否正在播放
pause暂停
prepare准备(同步)
prepareAsync准备(异步)
release释放MediaPlayer对象相关的资源
reset重置MediaPlayer对象为刚刚创建的状态
seekTo指定播放的位置(以毫秒为单位的时间)
setAudioStreamType设置流媒体的类型
setDataSource设置多媒体数据来源(位置)
setDisplay设置用SurfaceHolder来显示多媒体
setLooping设置是否循环播放
setOnButteringUpdateListener网络流媒体的缓冲监听
setOnErrorListener设置错误信息监听
setOnVideoSizeChangedListener视频尺寸监听
setScreenOnWhilePlaying设置是否使用SurfaceHolder来保持屏幕显示
setVolume设置音量
start开始播放
stop停止播放

  VideoView和MediaPlaer也比较类似,主要有以下常用方法:

setVideoPath()

设置要播放的视频文件的位置。

start()

开始或继续播放视频。

pause()

暂停播放视频。

resume()

将视频重头开始播放。

seekTo()

从指定的位置开始播放视频。

isPlaying()

判断当前是否正在播放视频。

getDuration()

获取载入的视频文件的时长。

8.4.1 播放音频

package com.dak.administrator.firstcode.multi_media;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

import java.io.File;
import java.io.IOException;

public class MediaActivity extends AppCompatActivity implements View.OnClickListener {

    private MediaPlayer mediaPlayer = new MediaPlayer();


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

        Button play = findViewById(R.id.play);
        Button pause = findViewById(R.id.pause);
        Button stop = findViewById(R.id.stop);

        play.setOnClickListener(this);
        pause.setOnClickListener(this);
        stop.setOnClickListener(this);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }else {
            initMediaPlayer();
        }

    }

    private void initMediaPlayer() {
        try {
            File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");//music.mp3不存在
            mediaPlayer.setDataSource(file.getPath());
            mediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            initMediaPlayer();
        }else {
            Toast.makeText(this, "请获取存储权限", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.play:
                if (!mediaPlayer.isPlaying()) {
                    mediaPlayer.start();
                }
                break;

            case R.id.pause:
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                }
                break;

            case R.id.stop:
                if (!mediaPlayer.isPlaying()) {
                    mediaPlayer.reset();
                    initMediaPlayer();
                }
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }

    }
}

xml文件:

​
<?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"
    android:orientation="vertical"
    tools:context="com.dak.administrator.firstcode.multi_media.MediaActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/play"
        android:text="play"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pause"
        android:text="pause"
        />

    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="stop" />


</LinearLayout>

​

8.4.2 播放视频

package com.dak.administrator.firstcode.multi_media;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.widget.VideoView;

import com.dak.administrator.firstcode.R;

import java.io.File;

public class VideoActivity extends AppCompatActivity implements View.OnClickListener {


    private VideoView videoView;

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

        Button play = findViewById(R.id.play);
        Button pause = findViewById(R.id.pause);
        Button replay = findViewById(R.id.replay);

        videoView = findViewById(R.id.video_view);

        play.setOnClickListener(this);
        pause.setOnClickListener(this);
        replay.setOnClickListener(this);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }else {
            initVideoPath();
        }
    }

    private void initVideoPath() {
        File file = new File(Environment.getExternalStorageDirectory(), "movie.mp4");
        videoView.setVideoPath(file.getPath());

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            initVideoPath();
        }else {
            Toast.makeText(this, "请获取存储权限", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.play:
                if (!videoView.isPlaying()) {
                    videoView.start();
                }
                break;
            case R.id.pause:
                if (videoView.isPlaying()) {
                    videoView.pause();
                }
                break;
            case R.id.replay:
                if (videoView.isPlaying()) {
                    videoView.resume();//重新播放
                }
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (videoView != null) {
            videoView.suspend();//释放资源
        }
    }
}

xml文件:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/play"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="play" />

        <Button
            android:id="@+id/pause"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="pause" />

        <Button
            android:id="@+id/replay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="replay" />

    </LinearLayout>

    <VideoView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/video_view"
        />
</LinearLayout>

8.5 小结与点评

郭霖总结:

本章我们主要对Android系统中的各种多媒体技术进行了学习,其中包括通知的使用校在模调用摄像头拍照从相册中选取照片, 以及播放香城和视频文件。由于所涉及的多媒体技术住快拟器上很难看得到效果,因此本章中还特意讲解了在Android手机上调试程序的方法。

又是充实饱满的一章啊!现在多媒体方面的知识已经学得足够多了,我希望你可以很好地将它们消化掉,尤其是与通知相关的内容,因为后面的学习当中还会用到它。目前我们所学的所有东西都仅仅是在本地上进行的,而实际上几乎市场上的每个应用都会涉及网络交互的部分,所比下一章中我们将会学习一下Android网络编程方面的内容。

我的总结:

在调用摄像头和相册这一点,我也是学到了非常多的东西,平时因为用一些框架,很少回去研究这些东西,如今算是学到了这些我们必要的一些知识。

因为MediaPlayer 主要是在音乐相关的项目上用到,所以也多了一些印象,VideoView的话 他在视频格式的支持以及播放效率方面都存在这较大的不足,所以建议我们只是用于播放一些游戏的片头动画,或者某个应用的视频宣传等。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值