闲置在家不用的Android手机有一两个都蒙尘了,想要把它们充分利用起来,可知道,现有的智能手机是可以充当Wifi摄像头来使用的,这就需要装一个App就能实现了,如果是用别的下载来APP安装用来会不会不放心呢,如果自己有能力,那就可以通过开发Android App项目过程来实现视频监控,有兴趣的来看看接下来的实现方案,
要完成整个过程,至少需要两部手机,一个手机用来充当WIFI摄像头(可以开启WIFI热点),另一个手机当视频监控用的,还是建议用WIFI路由器,就看中它信号强,网络又稳定
关于能看懂此文章的条件
- 会使用Android Studio开发工具
- 熟悉Java编程语言,开发过Android App
- 对WIFI路由器设置和网络信息收发报文
TCP,UDP原理有过了解
1.首先,打开Android Studio开发工具,选择新建Android 项目,使用Java语言,模板就选择 Emtpy Activity,在activity_main.xml文件中做好布局,具体布局内容太多这里就不贴了,自己布局就好,拖放组件是很简单的操作,只需要放三个按钮组件即可,分别是扫描摄像头,开启摄像头,退出APP,其它的不重要

2. 然后,在MainActivity.class上写代码,实现能打开按钮对应的页面即可,请看如下代码,其中用到的一些类,例如DeviceInfo.class, Common.class, BaseBackActivity.class这些就不贴了,看注释,具体的请等在后面提供的项目源码里看
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...这里省略了,只是处理了对标题栏的隐藏
setContentView(R.layout.activity_main);
//获取布局中的按钮组件
Button btnScan = findViewById(R.id.button_scan);
Button btnPreview = findViewById(R.id.button_preview);
Button btnExit = findViewById(R.id.buttonExit);
final Context context = MainActivity.this;
//设置点击事件
btnExit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
MainActivity.this.finish();//退出
}
});
btnScan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//初始化设备信息,包括了手机的摄像头相关属性,如名称,IP,数量
DeviceInfo di = DeviceInfo.init(context);
//...省略了一些判断细节,如判断IP是否正确,判断摄像头的网络状态
//打开扫描局域网内的摄像头页面
BaseBackActivity.navigateTo(MainActivity.this, ScanActivity.class, di);
}
});
btnPreview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//初始化设备信息
DeviceInfo di = DeviceInfo.init(context);
//...省略了一些判断细节,这一步是判断摄像头的授权
if(Common.requestCameraPermission(MainActivity.this)){
//打开WIFI摄像头的预览页面
BaseBackActivity.navigateTo(MainActivity.this, PreviewActivity.class, di);
}
}
});
}
}
接下来,做一个扫描摄像头页面的布局,文件是activity_scan.xml,大致布局如下图所示,运行后的效果图,就一个ListView展示列表的组件,还有标题栏上的搜索图标,那是扫描按钮

接着,创建一个对应页面的类ScanActivity.class 文件后,写上代码,如下
/**
* 扫描摄像头窗口
* */
public class ScanActivity extends BaseBackActivity {
//定义扫描线程
private ScanThread thread;
//定义列表组件
private ListView list;
//定义对初始化扫描的判断值
private boolean isFirstScan = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
//省略了,处理初始化标题栏的
//获取上一页传来的设备信息对象, getSerializable()是来自父类BaseBackActivity的方法
DeviceInfo di = (DeviceInfo) getSerializable();
//创建线程时,传入设备信息对象
thread = new ScanThread(this, di, new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//处理线程传来的消息
switch (msg.what) {
//扫描完成通知
case BaseThread.MESSAGE_SUCCESS:
{
//获取扫描后的局域网内所有可用的摄像头
ArrayList<RemoteCamera> cameras = thread.getCameras();
if(cameras.isEmpty()) {
//showToast方法来自父类,弹出提示
showToast(ScanActivity.this, "找不到可用的摄像头!");
}else{
//更新摄像头列表显示的
CamerasAdapter adapter = new CamerasAdapter(ScanActivity.this, cameras);
list.setAdapter(adapter);
list.invalidate();
showToast(ScanActivity.this, "扫描完成!");
}
}
break;
//扫描失败,或更新状态
case ScanThread.MESSAGE_FAIL:
case ScanThread.MESSAGE_LOADING:
showToast(ScanActivity.this, (String) msg.obj);
break;
default:
}
}
});
list = findViewById(R.id.listview);
//列表的点击事件
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
RemoteCamera camera = thread.getCameras().get(i);
//打开远程摄像头连接页面,传递一个摄像头信息camera
navigateTo(ScanActivity.this, RemoteActivity.class, camera);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//...此处省略,加载菜单布局的
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
//监听菜单按钮
switch (item.getItemId()) {
//扫描图标按钮被点击
case R.id.app_bar_search:
thread.startScanCamera();
return true;
default:
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
//第一次打开页面就扫描
if (!isFirstScan) {
thread.startScanCamera();
isFirstScan = true;
}
}
}
public class ScanThread extends BaseThread {
private ArrayList<RemoteCamera> cameras;
private DeviceInfo info;
//定义一个扫描线程
private Thread scanThread = null;
public ScanThread(Activity context, DeviceInfo info, Handler handler) {
//传参给父类BaseThread的构造方法,初始化
super(context, handler);
this.info = info;
this.cameras = new ArrayList<RemoteCamera>();
//来自父类的线程,用于处理接收的
thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
//初始化端口
mSocket = new DatagramSocket(null);
//...
mSocket.bind(new InetSocketAddress(SenderThread.FIND_CAMERA_PORT));
while(!Thread.interrupted()) {
//定一个空的数据报文
DatagramPacket pack = new DatagramPacket(new byte[1028], 1028);
//用空数据报文来接收数据,这时会一直等待,阻塞
mSocket.receive(pack);
//收到时,将报文里的数据转换成字符串
String s = new String(pack.getData(), 0, pack.getLength());
//在把字符串转成字符串数组,将接收到数据按照约定的协议转换一下
String[] datas = Common.getDeviceData(s);
//...此处省略,处理拿到count, 是摄像头数量,添加到cameras中
cameras.add(new RemoteCamera(cameras.size(), datas[0], count, datas[2]));
//发完成提示消息
showToast("扫到一个摄像头", MESSAGE_SUCCESS);
}
} catch (Exception e) {
showToast(e.getMessage());//遇到错误!
} finally {
cancelScan(true);
}
}
});
thread.start();
}
public void startScanCamera() {
//...次数省略判断的细节,下一步提示用户扫描中,建一个线程处理
showToast("扫描中...", MESSAGE_LOADING);
scanThread = new Thread(new Runnable() {
@Override
public void run() {
try {
//...此处省略一些细节,定义data数据
// 定义局域网的广播地址,这样表示 *.*.*.255,
InetAddress cameraAddress = InetAddress.getByName(Common.getWanIP(info.getLocalIp())+"255");
// 将data数据封装到报文中,还有IP地址,FIND_CAMERA_PORT 是 30000
DatagramPacket pack = new DatagramPacket(data, data.length, cameraAddress, FIND_CAMERA_PORT);
//将数据报文发送到广播地址,只要是连接到此局域网内的所有设备开放的30000端口都会收到该广播报文
mSocket.send(pack);
} catch (Exception e) {
showToast(e.getMessage());//遇到错误!
} finally {
//处理完后取消操作
cancelScan(false);
}
}
});
scanThread.start();
}
//判断是否在扫描
public boolean isScaning() {
return scanThread!=null;
}
public ArrayList<RemoteCamera> getCameras() {
return cameras;
}
//取消扫描
public void cancelScan(boolean isCancelAll) {
if(isCancelAll) {
//处理来自父类的方法
cancelThread();
}
if (isScaning()) {
scanThread.interrupt();
scanThread = null;
}
}
}
💡小提示
注意到创建的页面都有继承BaseBackActivity.class类,创建的线程都有继承类BaseThread.class,具体怎么写的,这里不详细讲了,那说下它的作用,它是相当于一个可以复用的类吧,类似模板,可以这样理解,稍微能明白,实现不会复杂
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="...">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 此处省略... -->
</manifest>
TextView组件,还有一个预览画面的SurfaceView组件放在中间,宽高分别是固定的320dp,240dp
💡小提示
有没有注意到,看视频监控上的状态栏,网络保持在23.3K/s每秒,这已经是一帧一帧的传输图像了,图像是320x240分辨率的,传输量会不会低了,可能有点卡吧,跟网络传输延迟有关的
public class RemoteActivity extends BaseBackActivity {
//定义一个网络接收的线程
private ReceiveThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remote);
//...省略了,处理初始化标题栏的
//获取上一页传来的设备信息对象, getSerializable()是来自父类BaseBackActivity的方法
RemoteCamera remote = (RemoteCamera) getSerializable();
//从布局中获取组件
SurfaceView view = findViewById(R.id.surfaceView2);
final TextView showState = findViewById(R.id.textView_state2);
//将远程设备信息设置到标题栏上
setTitle("远程摄像头:"+remote.toString());
//先获取焦点,然后设置屏幕长亮
view.setFocusable(true);
view.setKeepScreenOn(true);
//建立一个接收线程,传一个远程设备信息对象,还有预览组件的holder用于更新画面
thread = new ReceiveThread(this, new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//...处理线程发来的消息提示
}
}, remote, view.getHolder());
}
@Override
protected void onPostResume() {
super.onPostResume();
//让线程开始接收工作
thread.startReceive();
}
@Override
protected void onDestroy() {
//当前页面关闭时,让线程结束工作
thread.cancelReceive();
super.onDestroy();
}
}
public class ReceiveThread extends BaseThread {
private RemoteCamera remote;
private SurfaceHolder holder;
public ReceiveThread(Activity context, Handler handler, RemoteCamera remote, SurfaceHolder holder) {
super(context, handler);
this.remote = remote;
this.holder = holder;
}
public void startReceive() {
if (thread!=null) {
return;
}
thread = new Thread(new Runnable() {
@Override
public void run() {
String errMsg = "未知错误";
//...
try {
//...
if (mSocket==null) {
mSocket = new DatagramSocket(null);
//用开放30000端口来接收 FIND_CAMERA_PORT
mSocket.bind(new InetSocketAddress(FIND_CAMERA_PORT));
//...
showToast("连接中...", MESSAGE_UPDATE_STATE);
//发送请求接收下一帧图片
sendRet(mSocket, baos);
//...
showToast("等待接收...", MESSAGE_UPDATE_STATE);
while(mSocket!=null) {
//...定义空的数据报文packet
try {
//接收中,等待,此处阻塞
mSocket.receive(packet);
}catch (SocketTimeoutException te) {
showToast("连接超时..."+getLocalDateTime(), MESSAGE_UPDATE_STATE);
//再次发送请求
sendRet(mSocket, baos);
//...继续循环,重新接收
continue;
}
//判断一帧图片baos数据是否接收完成
if(packet.getLength() == endlen) {
String end = new String(packet.getData(), 0, endlen);
if(end.startsWith(PACKET_END)) {
//...获取time时间数据,下一步更新显示
updateViewDisplay(baos, time);
//设置接收下一帧等待时长,至少每100ms接收下一帧,可以设置更小,让视频看着更流畅
Thread.sleep(100);
baos.flush();
//再次发送请求
sendRet(mSocket, baos);
//...
showToast("接收中..."+getLocalDateTime(), MESSAGE_UPDATE_STATE);
continue;
}
}
//接收一帧图片数据流
baos.write(packet.getData(), 0, packet.getLength());
}
}
} catch (Exception e) {
errMsg = e.getMessage();//断开连接!;
} finally {
//...
cancelThread(errMsg);
}
}
});
thread.start();
}
private void sendRet(DatagramSocket dSocket, ByteArrayOutputStream baos) throws IOException, Exception {
//省略...处理发送接收下一帧图片请求
InetAddress address = InetAddress.getByName(remote.getIp());
DatagramPacket pack = new DatagramPacket(data, data.length, address, FIND_CAMERA_PORT);
dSocket.send(pack);
//...
}
private void updateViewDisplay(final ByteArrayOutputStream baos, final String time) {
//省略...处理转换图片
context.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//...锁定中,从组件中获取画布Canvas
Canvas canvas = holder.lockCanvas(null);
//...将图片画组件中,让用户可以看到
canvas.drawBitmap(bitmap2, 0, 0, null);
//...画上时间
canvas.drawText(time, 20, 30, p);
//解除锁定
holder.unlockCanvasAndPost(canvas);
//...
}
});
}
public void cancelReceive() {
cancelThread();
}
}
接下来,做一个开启摄像头页面的布局,文件是activity_preview.xml,大致布局如下图所示,是运行后的效果图,同上面讲过,跟远程摄像头页面布局那个是一样的,现在是有多放了一个选择摄像头的下拉框组件Spinner

接着,创建一个对应的页面类PreviewActivity.class文件,写上代码,参考如下
public class PreviewActivity extends BaseBackActivity {
private Camera camera = null;
private SurfaceHolder holder;
private Spinner seletep;
private int selectCameraId = 0;
//定义发送图片的线程
private SenderThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preview);
//...
final DeviceInfo info = (DeviceInfo) getSerializable();
//...获取布局中的组件,SurfaceView是绘制组件
final SurfaceView view = findViewById(R.id.surfaceView);
seletep = findViewById(R.id.spinner);
final TextView stateView = findViewById(R.id.textView_state);
//创建一个发送图片的线程,传入设备信息对象
thread = new SenderThread(this, info, new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//...处理线程发来的消息
}
});
String localIp = thread.getLocalIp();
String name = thread.getDeviceName();
setTitle("设备名:"+name+ ", 局域网IP:"+localIp);
seletep.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
//...切换摄像头
}
});
//先获取焦点
view.setFocusable(true);
//然后设置屏幕长亮
view.setKeepScreenOn(true);
holder = view.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
//绘制组件创建,准备摄像头
int cameraCount = Camera.getNumberOfCameras();
//...
thread.setCameraCount(cameraCount);
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
//绘制组件大小改变,重置摄像头
//...
openCamera();
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
thread.cancelThread();
//绘制组件销毁,释放摄像头资源
closeCamera();
}
});
}
@Override
protected void onPostResume() {
super.onPostResume();
//可被局域网内发现摄像头
thread.canFind(true);
}
private void openCamera() {
//...
try{
camera = Camera.open(selectCameraId);
Camera.Parameters params = camera.getParameters();
List<Camera.Size> sizes = params.getSupportedPictureSizes();
//图像大小
final int PICTURE_WIDTH = 320, PICTURE_HEIGHT = 240;
Camera.Size size = null;
//省略细节...查找摄像头配置参数,赋值图像大小
params.setPreviewSize(size.width, size.height);
params.setPreviewFrameRate(20);
params.setPictureFormat(PixelFormat.YCbCr_420_SP);
camera.setParameters(params);
//讲摄像头的图像设置到绘制组件中
camera.setPreviewDisplay(holder);
camera.setPreviewCallback(new Camera.PreviewCallback(){
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
//...省略细节...处理摄像头传来的图片,将bytes转换成image,当然可以不转换,直接发送更高效吧
//交给线程去发送
thread.setSendCameraImage(image);
}
});
camera.startPreview();
} catch (Exception e) {
showToast(this, "开启摄像头遇到了错误!");
}
}
void closeCamera() {
//...
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
camera = null;
}
}
public class SenderThread extends BaseThread {
private int cameraCount = 0;
public boolean isSending = false;
private DeviceInfo info;
private YuvImage image = null;
public SenderThread(Activity context, DeviceInfo info, Handler handler){
super(context, handler);
this.info = info;
}
//...
public void setCameraCount(int cameraCount) {
this.cameraCount = cameraCount;
}
public void canFind(boolean isFind) {
if(isFind==true && thread==null) {
this.thread = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
String errMsg = "未知错误";
try {
mSocket = new DatagramSocket(null);
//...绑定开放的30000端口
mSocket.bind(new InetSocketAddress(FIND_CAMERA_PORT));
do {
//...
DatagramPacket pack = new DatagramPacket(new byte[1028], 1028);
try {
//接收数据,等待中,会阻塞
mSocket.receive(pack);
}catch (Exception e){
e.printStackTrace();
throw e;
}
//获取发来请求的设备地址
SocketAddress sendAddress = pack.getSocketAddress();
//...
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(pack.getData()));
Integer code = (Integer) ois.readObject();
//...
switch (code){
case GET_CAMERA_IP:
{
//...判断请求1,将摄像头的数据包装成data,封装在报文中回发过去
DatagramPacket packet = new DatagramPacket(data, data.length, sendAddress);
mSocket.send(packet);
}
break;
case RET_CAMERA_IP:
{
isSending = true;
//...判断请求2,处理一帧图片回发过去
sendImage(image, sendAddress, sendTime);
isSending = false;
}
break;
default:
}
}while (!thread.isInterrupted() && mSocket!=null);
} catch (Exception e) {
errMsg = e.getMessage();
} finally {
cancelThread(errMsg);
}
}
});
this.thread.start();
}else{
cancelThread();
}
}
private void sendImage(YuvImage image, SocketAddress sendAddress, String sendTime) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
//将图片转换成数据流,压缩了图片就变小,减少传输量
image.compressToJpeg(new Rect(0,0, image.getWidth(), image.getHeight()), 80, outStream);
//...定义缓存大小,转换图片数据流
byte[] buffer = new byte[1024];
ByteArrayInputStream bais = new ByteArrayInputStream(outStream.toByteArray());
try{
int len;
DatagramPacket pack;
//...读取图片数据流,并拆分几次分发出去
while((len = bais.read(buffer, 0, buffer.length)) != -1) {
pack = new DatagramPacket(buffer, len, sendAddress);
mSocket.send(pack);
}
//分发完成后,最后发一个结束信息,告诉接收方这一帧图片已发完
byte[] end = (PACKET_END+sendTime).getBytes();
pack = new DatagramPacket(end, end.length, sendAddress);
mSocket.send(pack);
}catch (Exception e){
e.printStackTrace();
}finally {
bais.close();
}
}
public void setSendCameraImage(YuvImage image) {
if (isSending()) {
return;
}
this.image = image;
}
public boolean isSending() {
return isSending;
}
}
开启AP隔离,再点保存就可以了,取消AP隔离这样能让局域网的各种设备可互相连通,不需要连接到互联网
💡 小提示
- 为了安全起见,路由器中不建议对访客开放的WIFI网络中禁用AP隔离哦,
- 有些路由器中有访客WIFI开关,这个是没有AP隔离可禁用的
- 没有WIFI路由器的话,可用其中的一个手机开启WIFI热点功能代替,然后安装上面开发的APP,点击开启摄像头按钮就可以了,其它的手机都能扫描到这个摄像头的

最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?
我在Ruby中遇到了一个关于Dir[]和File.join()的简单程序,blobs_dir='/path/to/dir'Dir[File.join(blobs_dir,"**","*")].eachdo|file|FileUtils.rm_rf(file)ifFile.symlink?(file)我有两个困惑:首先,File.join(@blobs_dir,"**","*")中的第二个和第三个参数是什么意思?其次,Dir[]在Ruby中有什么用?我只知道它等价于Dir.glob(),但是,我对Dir.glob()确实不是很清楚。 最佳答案
1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>
一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su
TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是
目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'
我是Ruby的新手,但过去两周我一直在对Chef测试进行大量研究。该测试使用ChefSpec和Fauxhai,但它看起来不是很“像ruby”,我希望社区能给我一些编码风格的建议。有没有更好的方法来编写这样的嵌套循环?Recipe/foo/recipes/default.rbpackage"foo"doaction:installendRecipe/foo/spec/default_spec.rbrequire'chefspec'describe'foo::default'doplatforms={"debian"=>['6.0.5'],"ubuntu"=>['12.04','10.04
假设一个使用类变量的简单ruby程序,classHolder@@var=99defHolder.var=(val)@@var=valenddefvar@@varendend@@var="toplevelvariable"a=Holder.newputsa.var我猜结果应该是99,但输出不是99。我想知道为什么。由于类变量的范围是类,我假设@@var="toplevelvariable"行不会影响类中的变量。 最佳答案 @@var是Holder的类变量。而顶层的@@var不是Holder的同名类变量@@var,是你在创建类Obj