Android MediaProjection 录屏H264流或者保存为MP4视频

下载地址:https://download.csdn.net/download/ink_s/13125347

 

 

 

权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
// 要申请的权限
private String[] permissions = {
        Manifest.permission.SYSTEM_ALERT_WINDOW,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
};

 

录屏服务

method = 1 或 2是保存或直接获取H246裸流 

数据回调

public interface RecordServiceListener {
    void onImageData(byte[] buf);
}

 

 

method = 3 MediaCodec + MediaMuxer 将H264数据保存为MP4

method = 4  使用 MediaRecorder

 



import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.view.Surface;

import com.inks.inkslibrary.Utils.L;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import static android.media.MediaCodec.CONFIGURE_FLAG_ENCODE;
import static android.media.MediaFormat.KEY_BIT_RATE;
import static android.media.MediaFormat.KEY_FRAME_RATE;
import static android.media.MediaFormat.KEY_I_FRAME_INTERVAL;


public class RecordService extends Service {
    private static final String TAG = RecordService.class.getSimpleName();
    private MediaProjection mediaProjection = null;

    private final IBinder mBinder = new RecordService.LocalBinder();
    private Context context;
    private VirtualDisplay mVirtualDisplay;
    private MediaCodec vEncoder;
    private boolean isVideoEncoder;
    private MediaCodec.BufferInfo vBufferInfo = new MediaCodec.BufferInfo();
    private Thread videoEncoderThread;
    private RandomAccessFile mH264DataFile = null;
    private int w;
    private int h;
    private static final int TIMEOUT_US = 0;

    private long lastTime = 0;

    private MediaMuxer mediaMuxer;
    private int mVideoTrack = -1;

    MediaRecorder mMediaRecorder;


    public interface RecordServiceListener {
        void onImageData(byte[] buf);
    }

    private RecordServiceListener recordServiceListener;

    @Override
    public void onCreate() {
        L.e("RecordService onCreate");

        super.onCreate();
        if (Build.VERSION.SDK_INT >= 26) {
            startForeground(0, new Notification());
        }
        context = this;

    }


    public void start(RecordServiceListener recordServiceListener, MediaProjection mediaProjection, int w, int h,int method) throws Exception {
        this.w = w;
        this.h = h;
        this.mediaProjection = mediaProjection;
        if (mediaProjection != null) {
            this.recordServiceListener = recordServiceListener;
            //prepareVideoEncoder(1280,720);
            prepareVideoEncoder(w, h);
            switch (method){
                case 1:
                    File file = new File(context.getExternalFilesDir(null)
                            + "/" + getDateTimeStr() + "_____A" + w + "X" + h + ".264");
                    try {
                        mH264DataFile = new RandomAccessFile(file, "rw");
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }

                    startVideoEncode1();

                    break;
                case 2:
                     file = new File(context.getExternalFilesDir(null)
                            + "/" + getDateTimeStr() + "_____B" + w + "X" + h + ".264");
                    try {
                        mH264DataFile = new RandomAccessFile(file, "rw");
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }

                    startVideoEncode2();
                    break;
                case 3:
                    file = new File(context.getExternalFilesDir(null)
                            + "/" + getDateTimeStr() + "_____C" + w + "X" + h + ".mp4");
                    mediaMuxer = new MediaMuxer(file.getPath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    startVideoEncode3();
                    break;

                case 4:
                    file = new File(context.getExternalFilesDir(null)
                            + "/" + getDateTimeStr() + "_____D" + w + "X" + h + ".mp4");
                    startMediaRecorder(w,h,file.getPath());

                    break;

            }


        } else {
            throw new RuntimeException("MediaProjection null");
        }
    }



    public void stop() {
        isVideoEncoder = false;

        if (null != vEncoder) {
            vEncoder.stop();
        }


        if (mediaMuxer != null) {
            mediaMuxer.stop();
            mediaMuxer.release();
        }

        if (mMediaRecorder != null) {
            mMediaRecorder.setOnErrorListener(null);
            mMediaRecorder.reset();
            mMediaRecorder.release();
        }


        if (mVirtualDisplay != null) mVirtualDisplay.release();
        if (mediaProjection != null) mediaProjection.stop();
    }


    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    break;


            }
        }
    };


    public void prepareVideoEncoder(int width, int height) throws IOException {
        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(KEY_BIT_RATE, width * height);
        format.setInteger(MediaFormat.KEY_WIDTH, width);
        format.setInteger(MediaFormat.KEY_HEIGHT, height);
        format.setInteger(KEY_FRAME_RATE, 20);
        format.setInteger(KEY_I_FRAME_INTERVAL, 1);
        // 当画面静止时,重复最后一帧,不影响界面显示(好像并没有什么用)
        format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / 20);

        MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        encoder.configure(format, null, null, CONFIGURE_FLAG_ENCODE);
        Surface surface = encoder.createInputSurface();
        mVirtualDisplay = mediaProjection.createVirtualDisplay("-display", width, height, 1,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);
        vEncoder = encoder;


    }


    //把h264裸流保存为.264文件
    public void startVideoEncode1() {
        if (vEncoder == null) {
            throw new RuntimeException("vEncoder null,请初始化视频编码器");
        }
        if (isVideoEncoder) {
            throw new RuntimeException("It's already started. Please stop.");

        }
        videoEncoderThread = new Thread() {
            @Override
            public void run() {
                try {
                    vEncoder.start();


                    while (isVideoEncoder && !Thread.interrupted()) {
                        try {

                            int outputBufferId = vEncoder.dequeueOutputBuffer(vBufferInfo, 0);

                            if (outputBufferId >= 0) {

                                // 有效输出
                                // 获取到的实时帧视频数据
                                ByteBuffer encodedData = vEncoder.getOutputBuffer(outputBufferId);

                                byte[] dataToWrite = new byte[vBufferInfo.size];
                                encodedData.get(dataToWrite, 0, vBufferInfo.size);
                                saveH264DataToFile(dataToWrite);
                                //  onEncodedAvcFrame(bb, vBufferInfo);

                                vEncoder.releaseOutputBuffer(outputBufferId, false);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                } catch (Exception e) {
                    isVideoEncoder = false;
                    if (null != vEncoder) {
                        vEncoder.stop();
                    }
                    if (mVirtualDisplay != null) mVirtualDisplay.release();
                    if (mediaProjection != null) mediaProjection.stop();

                }
            }
        };
        isVideoEncoder = true;
        videoEncoderThread.start();
    }


    //把h264裸流回调给调用方或保存为.264文件,修改onEncodedAvcFrame选择是保存还是回调
    public void startVideoEncode2() {
        if (vEncoder == null) {
            throw new RuntimeException("vEncoder null,请初始化视频编码器");
        }
        if (isVideoEncoder) {
            throw new RuntimeException("It's already started. Please stop.");

        }
        videoEncoderThread = new Thread() {
            @Override
            public void run() {
                try {
                    vEncoder.start();
                    while (isVideoEncoder && !Thread.interrupted()) {
                        try {
                            int outputBufferId = vEncoder.dequeueOutputBuffer(vBufferInfo, 0);
                            if (outputBufferId >= 0) {
                                ByteBuffer bb = vEncoder.getOutputBuffer(outputBufferId);
                                onEncodedAvcFrame(bb, vBufferInfo);
                                vEncoder.releaseOutputBuffer(outputBufferId, false);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            break;
                        }
                    }


                } catch (Exception e) {
                    isVideoEncoder = false;
                    if (null != vEncoder) {
                        vEncoder.stop();
                    }
                    if (mVirtualDisplay != null) mVirtualDisplay.release();
                    if (mediaProjection != null) mediaProjection.stop();
                }


            }
        };
        isVideoEncoder = true;
        videoEncoderThread.start();
    }


    //MediaCodec + MediaMuxer 将H264数据保存为MP4
    public void startVideoEncode3() {
        if (vEncoder == null) {
            throw new RuntimeException("vEncoder null,请初始化视频编码器");
        }
        if (isVideoEncoder) {
            throw new RuntimeException("It's already started. Please stop.");

        }
        videoEncoderThread = new Thread() {
            @Override
            public void run() {
                try {
                    vEncoder.start();
                    while (isVideoEncoder && !Thread.interrupted()) {
                        try {
                            int index = vEncoder.dequeueOutputBuffer(vBufferInfo, TIMEOUT_US);

                            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                                // 后续输出格式变化
                                MediaFormat newFormat = vEncoder.getOutputFormat();
                                mVideoTrack = mediaMuxer.addTrack(newFormat);
                                mediaMuxer.start();

                            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                                // 请求超时
                                try {
                                    // wait 10ms
                                    Thread.sleep(10);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            } else if (index >= 0) {
                                // 有效输出
                                // 获取到的实时帧视频数据
                                ByteBuffer encodedData = vEncoder.getOutputBuffer(index);

                                if ((vBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                                    L.e("ignoring BUFFER_FLAG_CODEC_CONFIG");
                                    vBufferInfo.size = 0;
                                }
                                if (vBufferInfo.size == 0) {
                                    L.e("info.size == 0, drop it.");
                                    encodedData = null;
                                } else {
                                }
                                if (encodedData != null) {
                                    mediaMuxer.writeSampleData(mVideoTrack, encodedData, vBufferInfo);
                                }

                                vEncoder.releaseOutputBuffer(index, false);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                            break;
                        }
                    }


                } catch (Exception e) {
                    isVideoEncoder = false;
                    if (null != vEncoder) {
                        vEncoder.stop();
                        vEncoder.release();
                        mediaMuxer.stop();
                        mediaMuxer.release();
                    }
                    if (mVirtualDisplay != null) mVirtualDisplay.release();
                    if (mediaProjection != null) mediaProjection.stop();
                }


            }
        };
        isVideoEncoder = true;
        videoEncoderThread.start();
    }




    //MediaRecorder
    public void startMediaRecorder(int w,int h,String mPath) {
        try{
            isVideoEncoder = true;

            initMediaRecorder(w,h,mPath);
            //在mediarecorder.prepare()方法后调用
            mVirtualDisplay = mediaProjection.createVirtualDisplay(TAG + "-display", w, h, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mMediaRecorder.getSurface(), null, null);
            mMediaRecorder.start();
        }catch (Exception e){
            e.printStackTrace();

            isVideoEncoder = false;
            if (null != vEncoder) {
                vEncoder.stop();
                vEncoder.release();
            }
            if (mMediaRecorder != null) {
                mMediaRecorder.setOnErrorListener(null);
                mMediaRecorder.reset();
                mMediaRecorder.release();
            }

            if (mVirtualDisplay != null) mVirtualDisplay.release();
            if (mediaProjection != null) mediaProjection.stop();

        }

    }



    public void initMediaRecorder(int w,int h,String mPath) {
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
       // mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setOutputFile(mPath);
        mMediaRecorder.setVideoSize(w, h);
        mMediaRecorder.setVideoFrameRate(20);
        mMediaRecorder.setVideoEncodingBitRate(w*h);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        try {
            mMediaRecorder.prepare();
        } catch (Exception e) {
            e.printStackTrace();

        }
    }










    //保存H264视频到本地
    private void saveH264DataToFile(byte[] dataToWrite) {
        try {
            mH264DataFile.write(dataToWrite, 0, dataToWrite.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static final int NAL_SLICE = 1;
    public static final int NAL_SLICE_DPA = 2;
    public static final int NAL_SLICE_DPB = 3;
    public static final int NAL_SLICE_DPC = 4;
    public static final int NAL_SLICE_IDR = 5;
    public static final int NAL_SEI = 6;
    public static final int NAL_SPS = 7;
    public static final int NAL_PPS = 8;
    public static final int NAL_AUD = 9;
    public static final int NAL_FILLER = 12;
    private byte[] sps_pps_buf;

    //https://blog.csdn.net/a289973483/article/details/79220988
    private void onEncodedAvcFrame(ByteBuffer bb, final MediaCodec.BufferInfo vBufferInfo) {
        int offset = 4;
        //判断帧的类型
        if (bb.get(2) == 0x01) {
            offset = 3;
        }
        int type = bb.get(offset) & 0x1f;
        if (type == NAL_SPS) {
            //[0, 0, 0, 1, 103, 66, -64, 13, -38, 5, -126, 90, 1, -31, 16, -115, 64, 0, 0, 0, 1, 104, -50, 6, -30]
            //打印发现这里将 SPS帧和 PPS帧合在了一起发送
            // SPS为 [4,len-8]
            // PPS为后4个字节

            sps_pps_buf = new byte[vBufferInfo.size];


            bb.get(sps_pps_buf);

            L.e(Arrays.toString(sps_pps_buf));
            //TODO
            /*
            final byte[] pps = new byte[4];
            final byte[] sps = new byte[vBufferInfo.size - 12];
            bb.getInt();// 抛弃 0,0,0,1
            bb.get(sps, 0, sps.length);
            bb.getInt();
            bb.get(pps, 0, pps.length);
            Log.d(TAG, "解析得到 sps:" + Arrays.toString(sps) + ",PPS=" + Arrays.toString(pps));
            */
        } else if (type == NAL_SLICE  /* || type == NAL_SLICE_IDR */) {
            final byte[] bytes = new byte[vBufferInfo.size];
            bb.get(bytes);
            //回调数据流
//            if (null != recordServiceListener) {
//                recordServiceListener.onImageData(bytes);
//            }

            saveH264DataToFile(bytes);

//            Log.v(TAG, "视频数据  " + Arrays.toString(bytes));

        } else if (type == NAL_SLICE_IDR) {
            // I帧,前面添加sps和pps
            final byte[] bytes = new byte[vBufferInfo.size];
            bb.get(bytes);

            byte[] newBuf = new byte[sps_pps_buf.length + bytes.length];
            System.arraycopy(sps_pps_buf, 0, newBuf, 0, sps_pps_buf.length);
            System.arraycopy(bytes, 0, newBuf, sps_pps_buf.length, bytes.length);
//            if (null != recordServiceListener) {
            //回调数据流
//                recordServiceListener.onImageData(newBuf);
//            }


            saveH264DataToFile(newBuf);


//            Log.v(TAG, "sps pps  " + Arrays.toString(sps_pps_buf));
//            Log.v(TAG, "视频数据  " + Arrays.toString(newBuf));
        }
    }


    @Override
    public IBinder onBind(Intent intent) {
        L.e("TCP:onBind");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        L.e("RecordService:onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        L.e("RecordService:onDestroy");

        mHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }

    public class LocalBinder extends Binder {
        public RecordService getService() {
            return RecordService.this;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        L.e("RecordService:onStartCommand");

        return super.onStartCommand(intent, flags, startId);
    }


    private static String getDateTimeStr() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH_mm_ss");
        Date date = new Date();
        String dateStr = simpleDateFormat.format(date);
        return dateStr;
    }


}

 

activity中调用



import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;

import com.inks.inkslibrary.Utils.ClickUtil;
import com.inks.inkslibrary.Utils.L;
import com.inks.inkslibrary.Utils.T;

import java.io.File;
import java.util.ArrayList;
import java.util.Objects;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    private MediaProjectionManager mMediaProjectionManager;
    private static final int REQUEST_CODE = 1;

    @BindView(R.id.width)
    EditText width;
    @BindView(R.id.height)
    EditText height;



    private int w;
    private int h;
 
    private RecordService recordService;
    private final ServiceConnection recordServiceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            recordService = ((RecordService.LocalBinder) service).getService();
            L.e("RecordService ");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e("RecordService", "RecordService启动失败");

        }
    };

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

        Intent recordServiceIntent = new Intent(this, RecordService.class);
        bindService(recordServiceIntent, recordServiceConn, BIND_AUTO_CREATE);

        ButterKnife.bind(this);



    }


    public void click(View view) {
        if (!ClickUtil.isFastDoubleClick((long) 200)) {
            switch (view.getId()){
                case R.id.start:
                    String wStr = width.getText().toString().trim();
                    String hStr = height.getText().toString().trim();

                    if(TextUtils.isEmpty(wStr) || TextUtils.isEmpty(hStr)){
                        T.showShort(this,"请输入宽高");
                        break;
                    }

                    try {
                        w = Integer.parseInt(wStr);
                        h =Integer.parseInt(hStr);
                    }catch (Exception e){
                        T.showShort(this,"请输入宽高");
                        break;
                    }

                    prepareScreen();
                    break;
                case R.id.stop:
                    recordService.stop();
                   
                    break;
   case R.id.clear:
                    cleanFilePath( Objects.requireNonNull(this.getExternalFilesDir(null)).getPath());
                   
                    break;
            }
        }

    }






    public void prepareScreen() {
        mMediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        assert mMediaProjectionManager != null;
        Intent captureIntent = mMediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(captureIntent, REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK || requestCode != REQUEST_CODE) return;

        try {
            recordService.start(recordServiceListener,mMediaProjectionManager.getMediaProjection(resultCode, data),w,h,4);
        } catch (Exception e) {
            e.printStackTrace();
        }




    }


    RecordService.RecordServiceListener recordServiceListener = new RecordService.RecordServiceListener() {
        @Override
        public void onImageData(byte[] buf) {

        }
    };






    public   void cleanFilePath(String path) {
        L.e("path");

        File file = new File(path);

        if (!file.exists()) return;

        if (file.isFile() || file.list() == null) {
            L.e("1");
            return;
        } else {
            File[] files = file.listFiles();
            for (File a : files) {
                cleanFile(a.getPath());
            }
        }


    }



    public   void cleanFile(String path) {

        File file = new File(path);

        if (!file.exists()) return;

        if (file.isFile() || file.list() == null) {
            file.delete();
        } else {
            File[] files = file.listFiles();
            for (File a : files) {
                cleanFile(a.getPath());
            }
            file.delete();
        }


    }


}

 

 

 

activity.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=".MainActivity">

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

        <EditText
            android:id="@+id/width"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:maxLength="4"
            android:maxLines="1"
            android:layout_margin="10dp"
            android:hint="宽"
            android:text="1280"
            android:singleLine="true"/>


        <EditText
            android:id="@+id/height"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:maxLength="4"
            android:maxLines="1"
            android:layout_margin="10dp"
            android:hint="高"
            android:text="720"
            android:singleLine="true"/>

    </LinearLayout>


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

    <Button
        android:id="@+id/start"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="开始"
        android:onClick="click"/>

    <Button
        android:id="@+id/stop"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="停止"
        android:onClick="click"/>

    </LinearLayout>



    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/clear"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="清空文件"
            android:onClick="click"/>

        <TextClock
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:format24Hour="yyyy年 MM月dd日hh时mm分ss秒"/>

    </LinearLayout>






</LinearLayout>

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值