Android5.0以上版本录屏实现
我录屏的方式是分别录制音频和视频,最后合并成mp4格式,比较麻烦,因为网上完整的教程比较少,所以我打算写一个完整版的,照着我的代码写完之后,至少是能够实现功能的,而不是简单的介绍下用法。
1既然是录制视频,我们应该有一个按钮控制开始和结束。
2在录制之前,需要先判断一下Android系统的版本是否大于5.0,并且动态申请一下权限(读写,录音,照相机),这一步可以在点开始按钮的时候执行
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(); } if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(); } if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(); } Intent intent = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { intent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(intent, 101);//正常情况是要执行到这里的,作用是申请捕捉屏幕 } else { ShowUtil.showToast(context, "Android版本太低,无法使用该功能"); }
3定义MediaProjection和MediaProjectionManager等一些其他必要的变量
boolean isrun = false;//用来标记录屏的状态private MediaProjectionManager mediaProjectionManager; private MediaProjection mediaProjection;//录制视频的工具private int width, height, dpi;//屏幕宽高和dpi,后面会用到 private ScreenRecorder screenRecorder;//这个是自己写的录视频的工具类,下文会放完整的代码 Thread thread;//录视频要放在线程里去执行
在onCreat里写好实例化
mediaProjectionManager = (MediaProjectionManager) context.getSystemService(MEDIA_PROJECTION_SERVICE);
WindowManager manager = this.getWindowManager();DisplayMetrics outMetrics = new DisplayMetrics();manager.getDefaultDisplay().getMetrics(outMetrics);width = outMetrics.widthPixels;height = outMetrics.heightPixels;dpi = outMetrics.densityDpi;
4我们在onActivityResult回调方法中,来处理返回的事件
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { ) { Toast.makeText(context, "缺少读写权限", Toast.LENGTH_SHORT).show(); return; } ) { Toast.makeText(context, "缺少录音权限", Toast.LENGTH_SHORT).show(); return; } ) { Toast.makeText(context, "缺少相机权限", Toast.LENGTH_SHORT).show(); return; } if (requestCode != 101) { Log.e("HandDrawActivity", "error requestCode =" + requestCode); } if (resultCode != RESULT_OK) { Toast.makeText(context, "捕捉屏幕被禁止", Toast.LENGTH_SHORT).show(); return; } mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data); if (mediaProjection != null) { screenRecorder = new ScreenRecorder(width, height, mediaProjection, dpi); } thread = new Thread() { @Override public void run() { screenRecorder.startRecorder();//跟ScreenRecorder有关的下文再说,总之这句话的意思就是开始录屏的意思 } }; thread.start(); binding.startPlayer.setText("停止");//开始和停止我用的同一个按钮,所以开始录屏之后把按钮文字改一下 isrun = true;//录屏状态改成真 }
5先放上ScreenRecorder代码,只想要结果的朋友呢,直接把类粘贴走,把报错的地方改一改(在我自己的项目里可是不报错的),就实现了录制屏幕的功能了,还想看看的,可以往下看看
import android.hardware.display.DisplayManager; 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.Build; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.Surface; import com.coremedia.iso.boxes.Container; import com.googlecode.mp4parser.authoring.Movie; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; import com.googlecode.mp4parser.authoring.tracks.AppendTrack; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class ScreenRecorder { private int mWidth, mHeight, mDensty; private MediaProjection mediaProjection; private MediaCodec.BufferInfo mBufferInfo; private MediaCodec mEncorder; private Surface mInputSurface; private MediaMuxer mMuxer; private boolean isQuit = false; private boolean mMuxerStarted = false; private int mTrackIndex; private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cache"; private MediaRecorder mediaRecorder; public ScreenRecorder(int mWidth, int mHeight, MediaProjection mediaProjection, int mDensty) { this.mWidth = mWidth; this.mHeight = mHeight; this.mediaProjection = mediaProjection; this.mDensty = mDensty; File file = new File(path); if (!file.exists()) { file.mkdirs(); } } public void startRecorder() { prepareRecorder(); startLuYin(); startRecording(); } public void stop() { isQuit = true; releaseEncorders(1); List<String> filePath = new ArrayList<>(); filePath.add(path + "/APlanyinpin.amr"); filePath.add(path + "/APlanshipin.mp4"); joinVideo(filePath, path); } public void destory() { releaseEncorders(0); } private void startLuYin() { File file = new File(path, "APlanyinpin.amr"); mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mediaRecorder.setOutputFile(file.getAbsolutePath()); try { mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity", "已经开始录音"); } catch (IOException e) { e.printStackTrace(); } } private void prepareRecorder() { mBufferInfo = new MediaCodec.BufferInfo(); //元数据,描述bytebuffer的数据,尺寸,偏移 //创建格式化对象 MIMI_TYPE 传入的 video/avc 是H264编码格式 MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); int frameRate = 45; format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate); try { mEncorder = MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface = mEncorder.createInputSurface(); mEncorder.start(); } catch (IOException e) { e.printStackTrace(); releaseEncorders(0); } } private void startRecording() { File saveFile = new File(path, "APlanshipin.mp4"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface, null, null); drainEncoder(); } } catch (Exception e) { e.printStackTrace(); } } private void drainEncoder() { while (!isQuit) { Log.e("TAG", "drain....."); int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0); if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat()); if (!mMuxerStarted && mTrackIndex >= 0) { mMuxer.start(); mMuxerStarted = true; Log.e("HandDrawActivity", "已经开始录屏"); } } if (bufferIndex >= 0) { Log.e("TAG", "drain...write.."); ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } if (mBufferInfo.size != 0) { if (mMuxerStarted) { bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } Log.e("HandDrawActivity", "已经结束录屏"); } private void releaseEncorders(int i) { if (mediaProjection != null) { mediaProjection.stop(); } mBufferInfo = null; if (mEncorder != null) { mEncorder.stop(); } mInputSurface = null; if (mMuxer != null && i == 1) { mMuxer.stop(); } if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } } private boolean joinVideo(List<String> filePaths, String resultPath) { Log.e("HandDrawActivity", "准备合成中"); boolean result = false; if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) { throw new IllegalArgumentException(); } if (filePaths.size() == 1) { // 只有一个视频片段,不需要合并 return true; } try { Movie[] inMovies = new Movie[filePaths.size()]; for (int i = 0; i < filePaths.size(); i++) { Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i)); File f = new File(filePaths.get(i)); if (f.exists()) { inMovies[i] = MovieCreator.build(filePaths.get(i)); } } // 分别取出音轨和视频 List<Track> videoTracks = new LinkedList<>(); List<Track> audioTracks = new LinkedList<>(); for (Movie m : inMovies) { for (Track t : m.getTracks()) { if (t.getHandler().equals("soun")) { audioTracks.add(t); } if (t.getHandler().equals("vide")) { videoTracks.add(t); } } } // 合并到最终的视频文件 Movie outMovie = new Movie(); if (audioTracks.size() > 0) { outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (videoTracks.size() > 0) { outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } Container mp4file = new DefaultMp4Builder().build(outMovie); // 将文件输出 File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4"); if (resultFile.exists() && resultFile.isFile()) { resultFile.delete(); } FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel(); mp4file.writeContainer(fc); fc.close(); Log.e("HandDrawActivity", "合成完毕"); // 合成完成后把原片段文件删除 for (String filePath : filePaths) { File file = new File(filePath); file.delete(); } result = true; HandDrawActivity.sendVideo(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } }
6从startRecorder方法说起
public void startRecorder() { prepareRecorder();//录视频前的准备 startLuYin();//直接录音频(不用准备) startRecording();//录视频 }
录音的方法private void startLuYin() { File file = new File(path, "APlanyinpin.amr"); mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//声音来源,麦克 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//音频格式,默认,其实就是上面定义好的amr了,除此之外还有mp4 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//编码格式,问题是我不知道编码格式对什么有影响,是音质高低还是文件大小还是解析快慢,等我有时间去专门研究一下 mediaRecorder.setOutputFile(file.getAbsolutePath()); try { mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity", "已经开始录音"); } catch (IOException e) { e.printStackTrace(); } }
//录视频前的准备工作private void prepareRecorder() { mBufferInfo = new MediaCodec.BufferInfo(); //元数据,描述bytebuffer的数据,尺寸,偏移 //创建格式化对象 MIMI_TYPE 传入的 video/avc 是H264编码格式 MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); int frameRate = 45; format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);//编码器的设置,具体是设置的啥我也不太清楚,但是网上查一查都是这么写的!!! try { mEncorder = MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface = mEncorder.createInputSurface(); mEncorder.start();//让编码器先跑起来 } catch (IOException e) { e.printStackTrace(); releaseEncorders(0); } }
这里也是准备工作private void startRecording() { File saveFile = new File(path, "APlanshipin.mp4"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);//百度一下MediaMuxer,讲的很详细的 mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface, null, null); drainEncoder(); } } catch (Exception e) { e.printStackTrace(); } }
这个就是开始写视频文件了private void drainEncoder() { while (!isQuit) { Log.e("TAG", "drain....."); int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0); if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat()); if (!mMuxerStarted && mTrackIndex >= 0) { mMuxer.start(); mMuxerStarted = true; Log.e("HandDrawActivity", "已经开始录屏"); } } if (bufferIndex >= 0) { Log.e("TAG", "drain...write.."); ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } if (mBufferInfo.size != 0) { if (mMuxerStarted) { bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } Log.e("HandDrawActivity", "已经结束录屏"); }
这个就是把录好的音频和视频合并成mp4的方法了,也是点击停止录屏的时候用到的private boolean joinVideo(List<String> filePaths, String resultPath) { Log.e("HandDrawActivity", "准备合成中"); boolean result = false; if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) { throw new IllegalArgumentException(); } if (filePaths.size() == 1) { // 只有一个视频片段,不需要合并 return true; } try { Movie[] inMovies = new Movie[filePaths.size()]; for (int i = 0; i < filePaths.size(); i++) { Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i)); File f = new File(filePaths.get(i)); if (f.exists()) { inMovies[i] = MovieCreator.build(filePaths.get(i)); } } // 分别取出音轨和视频 List<Track> videoTracks = new LinkedList<>(); List<Track> audioTracks = new LinkedList<>(); for (Movie m : inMovies) { for (Track t : m.getTracks()) { if (t.getHandler().equals("soun")) { audioTracks.add(t); } if (t.getHandler().equals("vide")) { videoTracks.add(t); } } } // 合并到最终的视频文件 Movie outMovie = new Movie(); if (audioTracks.size() > 0) { outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (videoTracks.size() > 0) { outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } Container mp4file = new DefaultMp4Builder().build(outMovie); // 将文件输出 File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4"); if (resultFile.exists() && resultFile.isFile()) { resultFile.delete(); } FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel(); mp4file.writeContainer(fc); fc.close(); Log.e("HandDrawActivity", "合成完毕"); // 合成完成后把原片段文件删除 for (String filePath : filePaths) { File file = new File(filePath); file.delete(); } result = true; HandDrawActivity.sendVideo(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; }
这个就是结束的时候了,该清空的清空,该注销的注销, i是用来判断录没录的,有可能刚进入这个页面都没录过,直接就返回到别的页面了,那就有可能空指针异常,因为有些变量都没初始化,所以用i判断一下,也可以自己写别的方法判端private void releaseEncorders(int i) { if (mediaProjection != null) { mediaProjection.stop(); } mBufferInfo = null; if (mEncorder != null) { mEncorder.stop(); } mInputSurface = null; if (mMuxer != null && i == 1) { mMuxer.stop(); } if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } }
7部分代码也是我从网上扒的,但是网上的代码就没怎么见过比较完整的版本的,我上面写的都是经过我自己测试绝对没问题的而且代码也没什么遗漏的,要是发现有遗漏的代码我后续再补上。
Android5.0以上版本录屏实现的更多相关文章
- Android5.0免Root截屏,录屏
http://blog.csdn.net/wds1181977/article/details/52174840 MediaProjection介绍 MediaProjection可以用来捕捉屏幕,具 ...
- Android5.0以后版本把应用移动到SD或者TF卡的方法
由于手机内存较小,才8G,用的时间一久,内部存储就满了,天天删垃圾,WIFI还老断线,终于忍无可忍了,决定把应用移动到SD卡,实践下来,只有少部分App默认支持移动到SD卡,大部分程序不支持只能装在内 ...
- 友情提醒:欲开发android5.0以上应用,请全部更新开发工具至最新
周末帮人完成一个项目,android5.0以上版本,谁知道被开发工具折腾的死去活来.我的开发环境是adt-bundle-windows-x86-20140702.zip版本,也是目前能找到的adt-b ...
- Fundebug录屏插件更新至0.6.0
摘要: 录屏插件的性能进一步优化,传输的数据体积大幅度减少. 录屏功能介绍 Fundebug提供专业的异常监控服务,当线上应用出现 BUG 的时候,我们可以第一时间报警,帮助开发者及时发现 BUG,提 ...
- Fundebug录屏插件更新至0.5.0,新增domain参数
摘要: 通过配置domain来保证"视频"的正确录制 录屏功能介绍 Fundebug提供专业的异常监控服务,当线上应用出现 BUG 的时候,我们可以第一时间报警,帮助开发者及时发现 ...
- Fundebug录屏插件更新至0.4.0,修复BUG,优化性能
摘要: 录屏功能更加强大,欢迎免费试用! 关于Fundebug录屏功能 Fundebug是专业的程序BUG监控服务,当线上应用出现BUG的时候,我们可以第一时间报警,帮助开发者及时发现BUG,提高De ...
- Android Material Design Ripple Effect在Android5.0(SDK=21)以下Android版本崩溃问题解决
Android Material Design Ripple Effect在Android5.0(SDK=21)以下Android版本崩溃问题解决 附录1的Android Ripple Effect水 ...
- 华为联运游戏审核驳回:在未安装或需更新HMS Core的手机上,提示安装,点击取消后,游戏卡屏(集成的6.1.0.301版本游戏SDK)
问题描述 更新游戏SDK到6.1.0.301版本之后,游戏包被审核驳回:在未安装或需更新华为移动服务版本(HMS Core)的手机上,提示安装华为移动服务(HMS Core),点击取消,游戏卡屏.修改 ...
- 手游录屏直播技术详解 | 直播 SDK 性能优化实践
在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...
随机推荐
- 三 vue学习三 从读懂一个Vue项目开始
源码地址: https://github.com/liufeiSAP/vue2-manage 我们的目录结构: 目录/文件 说明 build 项目构建(webpack)相关代码. config ...
- python os.system重定向stdout到变量 ,同时获取返回值
Python执行系统命令的方法 os.system(),os.popen(),commands 最近在做那个测试框架的时候发现 Python 的另一个获得系统执行命令的返回值和输出的类. 最开始的时候 ...
- Win 7下破解Loadrunner 11(带中文版下载地址)
空间管理您的位置: 51Testing软件测试网 » 测试是一种生活态度 » 日志 与您一起分享在测试过程中的快乐与辛酸... Win 7下破解Loadrunner 11(带中文版下载地址) 上一篇 ...
- checkbox的几种遍历方法
html代码如下: <div> <input type="checkbox" name="ckb" value="1" / ...
- codevs-1203
1203 判断浮点数是否相等 题目描述 Description 给出两个浮点数,请你判断这两个浮点数是否相等 输入描述 Input Description 输入仅一行,包含两个浮点数 输出描述 ...
- UVa 10755 Garbage Heap (暴力+前缀和)
题意:有个长方体由A*B*C组成,每个废料都有一个价值,要选一个子长方体,使得价值最大. 析:我们暴力枚举上下左右边界,然后用前缀和来快速得到另一个,然后就能得到长方体,每次维护一个最小值,然后差就是 ...
- UVa 1641 ASCII Area (计算几何,水题)
题意:给定一个矩阵,里面有一个多边形,求多边形的面积. 析:因为是在格子里,并且这个多边形是很规则的,所以所有格子不是全属于多边形就是全不属于,或者一半,并且我们可以根据"/"和“ ...
- WindowsService服务程序开发 安装和卸载
安装服务:installutil.exe E:\XTestDemo\X_15_WindowsService\bin\Debug\X_15_WindowsService.exe 卸载服务:install ...
- Inside Geometry Instancing(下)
Inside Geometry Instancing(下) http://blog.csdn.net/soilwork/article/details/655858 此教程版权归我所有,仅供个人学习使 ...
- __setitem__,__getitem,__delitem__
目录 __setitem__ __getitem__ __delitem__与__delattr__ class Foo: def __init__(self, name): self.name = ...