Android Socket使用TCP協議實現手機投屏

本節主要通過實戰來了解Socket在TCP/IP協議中充當的是一個什么角色,有什么作用。通過Socket使用TCP協議實現局域網內手機A充當服務端,手機B充當客戶端,手機B連接手機A,手機A獲取屏幕數據轉化為Bitmap,通過Socket傳遞個手機B顯示。

實現效果:

一、?Socket是什么?

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

主機?A?的應用程序要能和主機?B?的應用程序通信,必須通過?Socket?建立連接,而建立?Socket?連接必須需要底層TCP/IP?協議來建立?TCP?連接。建立?TCP?連接需要底層?IP?協議來尋址網絡中的主機。我們知道網絡層使用的?IP?協議可以幫助我們根據?IP?地址來找到目標主機,但是一臺主機上可能運行著多個應用程序,如何才能與指定的應用程序通信就要通過?TCP?或?UPD?的地址也就是端口號來指定。這樣就可以通過一個?Socket?實例唯一代表一個主機上的一個應用程序的通信鏈路了。?

短連接:連接->傳輸數據->關閉連接:

傳統HTTP是無狀態的,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。也可以這樣說:短連接是指SOCKET連接后發送后接收完數據后馬上斷開連接。

長連接:連接->傳輸數據->保持連接?->?傳輸數據-> ……?->關閉連接:

長連接指建立SOCKET連接后不管是否使用都保持連接。

什么時候用長連接,短連接?

長連接多用于操作頻繁,點對點的通訊,而且連接數不能太多情況,。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,下次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接,?如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket?創建也是對資源的浪費。

而像WEB網站的http服務一般都用短鏈接,因為長連接對于服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源。

總之,長連接和短連接的選擇要視情況而定。?而我們接下來要實現的手機實時投屏效果使用的就是長連接。

二、Socket的使用:

在使用Socket時,我們會使用到ServiceSocket和Socket,ServerSocket負責綁定IP地址,啟動監聽端口;Socket負責發起連接操作。連接成功后,雙方通過輸入和輸出流進行同步阻塞式通信。

1、創建TcpServerRunnable TCP服務端

1)由于Android在使用網絡通訊時要放在子線程中執行,所以可以將TcpServerRunnable實現Runnable接口。

public class TcpServerRunnable implements Runnable {@Overridepublic void run() {}
}

2)在執行run方法里面創建ServiceSocket,ServerSocket內部使用的是TCP協議,如果想要使用UDP協議可以使用DatagramSocket。

private boolean ServerCreate() {try {serverSocket = new ServerSocket(port, 1);} catch (Exception e) {e.printStackTrace();if (listener != null) {listener.onServerClose();}return false;}return true;
}

3)創建成功后,開啟?while 循環監聽TCP服務端是否被客戶端連接;

private void ServerRun() {if (!ServerCreate()) {return;}while (true) {if (!ServerListen()) {break;}}
}private boolean ServerListen() {try {socket = serverSocket.accept();} catch (Exception e) {e.printStackTrace();return false;}if (listener != null) {listener.onServerConnect();}return true;
}

4)當有客戶端連接到服務端時,開啟?while 循環,從內存中拿取bitmap(屏幕數據),組裝協議數據,發送給客戶端。

private void ServerRun() {if (!ServerCreate()) {return;}while (true) {if (!ServerListen()) {break;}while (ServerIsConnect()) {ServerTransmitBitmap();ServerSleep(10);}}
}private final static byte[] PACKAGE_HEAD = {(byte) 0xFF, (byte) 0xCF, (byte) 0xFA, (byte) 0xBF, (byte) 0xF6, (byte) 0xAF, (byte) 0xFE, (byte) 0xFF};/*** 寫入 協議頭+投屏bitmap數據*/
private void ServerTransmitBitmap() {try {DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());if (bitmap != null) {byte[] bytes = MyUtils.BitmaptoBytes(bitmap);dataOutputStream.write(PACKAGE_HEAD);dataOutputStream.writeInt(MyUtils.getScreenWidth());dataOutputStream.writeInt(MyUtils.getScreenHeight());dataOutputStream.writeInt(bytes.length);dataOutputStream.write(bytes);}dataOutputStream.flush();} catch (IOException e) {e.printStackTrace();}
}

當執行到dataOutputStream.flush();時,就會將數據發送給客戶端,由于ServerTransmitBitmap()方法是在?while 循環里面的,所以當手機屏幕數據刷新后,重新賦值給bitmap,服務端會自動將新的bitmap再次組裝發送給客戶端,實現投屏實時刷新的效果。

public class TcpServerRunnable implements Runnable {private static final String TAG = "TcpServerRunnable";private ServerSocket serverSocket;private Socket socket;private int port;private Bitmap bitmap;public void setPort(int port) {this.port = port;}public void setBitmap(Bitmap bitmap) {this.bitmap = bitmap;}@Overridepublic void run() {ServerRun();}/*** 運行服務端*/private void ServerRun() {if (!ServerCreate()) {return;}while (true) {if (!ServerListen()) {break;}while (ServerIsConnect()) {ServerTransmitBitmap();ServerSleep(10);}}}/*** 使用ServerSocket創建TCP服務端** @return*/private boolean ServerCreate() {try {serverSocket = new ServerSocket(port, 1);} catch (Exception e) {e.printStackTrace();if (listener != null) {listener.onServerClose();}return false;}return true;}/*** 循環監聽服務端是否被連接** @return*/private boolean ServerListen() {try {socket = serverSocket.accept();} catch (Exception e) {e.printStackTrace();return false;}if (listener != null) {listener.onServerConnect();}return true;}/*** 判斷服務端是否被連接** @return*/private boolean ServerIsConnect() {return socket != null && !socket.isClosed() && socket.isConnected();}private final static byte[] PACKAGE_HEAD = {(byte) 0xFF, (byte) 0xCF, (byte) 0xFA, (byte) 0xBF, (byte) 0xF6, (byte) 0xAF, (byte) 0xFE, (byte) 0xFF};/*** 寫入 協議頭+投屏bitmap數據*/private void ServerTransmitBitmap() {try {DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());if (bitmap != null) {byte[] bytes = MyUtils.BitmaptoBytes(bitmap);dataOutputStream.write(PACKAGE_HEAD);dataOutputStream.writeInt(MyUtils.getScreenWidth());dataOutputStream.writeInt(MyUtils.getScreenHeight());dataOutputStream.writeInt(bytes.length);dataOutputStream.write(bytes);}dataOutputStream.flush();} catch (IOException e) {e.printStackTrace();}}private void ServerSleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}}/*** 關閉連接*/public void close() {ServerClose();}private void ServerClose() {try {if (socket != null) {socket.close();serverSocket.close();}} catch (Exception e) {e.printStackTrace();}if (listener != null) {listener.onServerClose();}}private ServerListener listener;public void setListener(ServerListener listener) {this.listener = listener;}public interface ServerListener {void onServerConnect();void onServerClose();}
}

2、獲取手機屏幕數據,并轉化為bitmap

1)創建ScreenCaptureService前臺服務,執行處理捕獲設備屏幕的單例類ScreenCapture。

public class ScreenCaptureService extends Service {private ScreenCapture screenCapture;public ScreenCaptureService() {}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {createNotificationChannel(); //創建通知欄,你正在錄屏}Bundle bundle = intent.getExtras();if (bundle != null) {int resultCode = bundle.getInt("resultCode");Intent data = bundle.getParcelable("resultData");screenCapture = ScreenCapture.getInstance(this, resultCode, data);}screenCapture.startScreenCapture();return START_STICKY;}@Overridepublic void onDestroy() {super.onDestroy();screenCapture.stopScreenCapture();}private void createNotificationChannel() {}
}

2)創建MediaProjection,捕獲設備屏幕上的內容,并將數據轉化為Bitmap,MyUtils.setBitmap(bitmap) 存儲到靜態變量中。

public class ScreenCapture implements ImageReader.OnImageAvailableListener{private static final String TAG = "ScreenCapture";private final MediaProjection mMediaProjection; // 用于捕獲設備屏幕上的內容并進行錄制或截圖private VirtualDisplay mVirtualDisplay;private final ImageReader mImageReader;private final int screen_width;private final int screen_height;private final int screen_density;private static volatile ScreenCapture screenCapture;@SuppressLint("WrongConstant")private ScreenCapture(Context context, int resultCode, Intent data) {MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);screen_width = MyUtils.getScreenWidth();screen_height = MyUtils.getScreenHeight();screen_density = MyUtils.getScreenDensity();mImageReader = ImageReader.newInstance(screen_width,screen_height,PixelFormat.RGBA_8888,2);}public static ScreenCapture getInstance(Context context, int resultCode, Intent data) {if(screenCapture == null) {synchronized (ScreenCapture.class) {if(screenCapture == null) {screenCapture = new ScreenCapture(context, resultCode, data);}}}return screenCapture;}public void startScreenCapture() {if (mMediaProjection != null) {setUpVirtualDisplay();}}private void setUpVirtualDisplay() {mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture",screen_width,screen_height,screen_density,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,mImageReader.getSurface(),null,null);mImageReader.setOnImageAvailableListener(this, null);}public void stopScreenCapture() {if (mVirtualDisplay != null) {mVirtualDisplay.release();}}@Overridepublic void onImageAvailable(ImageReader imageReader) {try {Image image = imageReader.acquireLatestImage();if(image != null) {Image.Plane[] planes = image.getPlanes();ByteBuffer buffer = planes[0].getBuffer();int pixelStride = planes[0].getPixelStride();int rowStride = planes[0].getRowStride();int rowPadding = rowStride - pixelStride * screen_width;Bitmap bitmap = Bitmap.createBitmap(screen_width + rowPadding / pixelStride, screen_height, Bitmap.Config.ARGB_8888);bitmap.copyPixelsFromBuffer(buffer);MyUtils.setBitmap(bitmap);image.close();}} catch (Exception e) {e.printStackTrace();}}
}

3、對獲取的屏幕Bitmap進行壓縮,降低Bitmap大小,加快Socket傳輸速度。(非必須,不壓縮也行)。創建BitmapProcessRunnable實現Runnable接口,在子線程執行bitmap壓縮操作。

public class BitmapProcessRunnable implements Runnable {private static final String TAG = "BitmapProcessRunnable";private boolean isRun = false;public void setRun(boolean isRun) {this.isRun = isRun;}@Overridepublic void run() {while (isRun) {try {Bitmap bitmap = MyUtils.getBitmap();Log.i(TAG, "bitmap:" + bitmap);if (bitmap != null) {bitmap = MyUtils.BitmapMatrixCompress(bitmap);if (listener != null) {listener.onProcessBitmap(bitmap);}}Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}private ProcessListener listener;public interface ProcessListener {void onProcessBitmap(Bitmap bitmap);}public void setListener(ProcessListener listener) {this.listener = listener;}
}
public static Bitmap BitmapMatrixCompress(Bitmap bitmap) {Matrix matrix = new Matrix();matrix.setScale(0.5f, 0.5f);return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

3、關聯TCP客戶端ServerFragment頁面

1)在ServerFragment頁面創建TcpServerRunnable(TCP服務端)和BitmapProcessRunnable(圖片處理線程),當用戶點擊創建按鈕時,調用MyUtils.ExecuteRunnable(tcpServerRunnable),執行TcpServerRunnable的run方法。

2)客戶端連接TCP服務端后,在回調接口onServerConnect()里面,開啟前臺服務ScreenCaptureService,捕獲屏幕數據,同時執行BitmapProcessRunnable的run方法,對獲取到的bitmap進行壓縮,壓縮完成,將新bitmap賦值給TcpServerRunnable(TCP服務端)。

3)TcpServerRunnable(TCP服務端)的 while 循環里面讀取到新的bitmap,進行組裝bitmap協議數據,發送給客戶端。

public class ServerFragment extends Fragment implements View.OnClickListener {private static final String TAG = "ServerFragment";private static boolean server_create = false;private TextView server_text;private Button create_button;private TcpServerRunnable tcpServerRunnable; // TCP服務端private BitmapProcessRunnable bitmapProcessRunnable; // 圖片處理線程@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_server, container, false);}@Overridepublic void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);server_text = view.findViewById(R.id.server_text);server_text.setText(MyUtils.getLocalAddress());create_button = view.findViewById(R.id.create_button);create_button.setOnClickListener(this);bitmapProcessRunnable = new BitmapProcessRunnable();bitmapProcessRunnable.setListener(bitmapProcessListener);tcpServerRunnable = new TcpServerRunnable();tcpServerRunnable.setPort(MyUtils.tcpSocketPort);tcpServerRunnable.setListener(tcpServerListener);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.create_button) {if (!server_create) {server_create = true;MyUtils.ExecuteRunnable(tcpServerRunnable);create_button.setText("關閉");} else {server_create = false;tcpServerRunnable.close();create_button.setText("創建");}}}@Overridepublic void onDestroy() {super.onDestroy();tcpServerRunnable.close();}TcpServerRunnable.ServerListener tcpServerListener = new TcpServerRunnable.ServerListener() {@Overridepublic void onServerConnect() {bitmapProcessRunnable.setRun(true);// 壓縮圖片MyUtils.ExecuteRunnable(bitmapProcessRunnable);Bundle bundle = new Bundle();Intent start = new Intent(getActivity(), ScreenCaptureService.class);bundle.putInt("resultCode", MyUtils.getResultCode());bundle.putParcelable("resultData", MyUtils.getResultData());start.putExtras(bundle);// 啟動投屏服務if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {getActivity().startForegroundService(start);} else {getActivity().startService(start);}}@Overridepublic void onServerClose() {bitmapProcessRunnable.setRun(false);Intent stop = new Intent(getActivity(), ScreenCaptureService.class);getActivity().stopService(stop);}};BitmapProcessRunnable.ProcessListener bitmapProcessListener = new BitmapProcessRunnable.ProcessListener() {@Overridepublic void onProcessBitmap(Bitmap bitmap) {// 將壓縮后的圖片傳給tcpServer,tcpServer發送給客戶端tcpServerRunnable.setBitmap(bitmap);}};
}

4、創建TcpClientRunnable TCP客戶端

1)同樣在子線程中開啟連接TCP服務端,這里需要知道服務端的IP地址和端口號。

@Override
public void run() {ClientRun();
}private void ClientRun() {if (!ClientConnect()) {return;}
}private boolean ClientConnect() {try {socket = new Socket(ip, port);} catch (Exception e) {e.printStackTrace();return false;}if (listener != null) {listener.onClientConnect();}return true;
}

2)連接成功后,開啟 while 循環,接收服務端發送過來的bitmap數據,賦值給靜態變量MyUtils.setBitmap()。

private void ClientRun() {if (!ClientConnect()) {return;}while (true) {while (ClientIsConnect()) {ClientReceiveBitmap();ClientSleep(10);}}
}private void ClientReceiveBitmap() {try {Log.i(TAG,"循環讀取服務端傳過來的投屏Bitmap");InputStream inputStream = socket.getInputStream();boolean isHead = true;for (byte b : PACKAGE_HEAD) {byte head = (byte) inputStream.read();if (head != b) {isHead = false;break;}}if (isHead) {DataInputStream dataInputStream = new DataInputStream(inputStream);int width = dataInputStream.readInt();int height = dataInputStream.readInt();int len = dataInputStream.readInt();byte[] bytes = new byte[len];dataInputStream.readFully(bytes, 0, len);Bitmap bitmap = MyUtils.BytestoBitmap(bytes);if (bitmap != null && width != 0 && height != 0) {if (listener != null) {listener.onClientReceiveBitmap(bitmap, width, height);}}}} catch (Exception e) {e.printStackTrace();}
}

5、客戶端創建顯示投屏的DisplayActivity

1)DisplayActivity通過自定義DisplayView來實時顯示投屏數據;

public class DisplayActivity extends AppCompatActivity {private static final String TAG = "DisplayActivity";private DisplayView displayView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_display);initView();}private void initView() {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);displayView = findViewById(R.id.displayView);displayView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {displayView.setViewWidth(displayView.getWidth());displayView.setViewHeight(displayView.getHeight());displayView.getViewTreeObserver().removeOnGlobalLayoutListener(this);}});}}

2)DisplayView繼承自SurfaceView,并實現Runnable接口,當DisplayView添加到Activity后,Surface第一次被創建時回調到void surfaceCreated(),然后在surfaceCreated方法里面啟動自己的run方法,循環的將bitmap繪制到頁面上。

public class DisplayView extends SurfaceView implements SurfaceHolder.Callback,  Runnable{private static final String TAG = "DisplayView";private int viewWidth;private int viewHeight;private Bitmap bitmap;private int width;private int height;private SurfaceHolder surfaceHolder;private boolean isDraw = false;public DisplayView(Context context) {super(context);initView();}public DisplayView(Context context, AttributeSet attrs) {super(context, attrs);initView();}public DisplayView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}private void initView() {surfaceHolder = getHolder();surfaceHolder.addCallback(this);surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);setZOrderMediaOverlay(true);}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {isDraw = true;MyUtils.ExecuteRunnable(this);}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {isDraw = false;}@Overridepublic void run() {while (isDraw) {try {drawBitmap();Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}public void drawBitmap() {Canvas canvas = surfaceHolder.lockCanvas();if (canvas != null) {bitmap = getBitmap();if (bitmap != null) {canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);Rect rect = new Rect(0, 0, viewWidth, viewHeight);canvas.drawBitmap(bitmap, null, rect, null);}surfaceHolder.unlockCanvasAndPost(canvas);}}public Bitmap getBitmap() {// return bitmap;return MyUtils.getShowBitmap();}public void setBitmap(Bitmap bitmap, int width, int height) {this.bitmap = bitmap;this.width = width;this.height = height;}public void setViewWidth(int width) {this.viewWidth = width;}public void setViewHeight(int height) {this.viewHeight = height;}
}

6、關聯TCP客戶端ClientFragment頁面

1)在ClientFragment頁面創建TcpClientRunnable(TCP客戶端端)當用戶輸入服務端IP地址,點擊連接按鈕時,調用MyUtils.ExecuteRunnable(tcpClientRunnable),執行TcpClientRunnable的run方法。

2)客戶端連接TCP服務端后,在回調接口onClientConnect()里面,開啟DisplayActivity,DisplayView創建,等待bitmap寫入MyUtils.setBitmap,循環的將bitmap繪制到頁面上。

3)當客戶端接收到服務端發送過來的投屏bitmap時,回調到onClientReceiveBitmap(),將bitmap設置到靜態變量里面MyUtils.setBitmap(bitmap, width, height)。

public class ClientFragment extends Fragment implements View.OnClickListener {private static final String TAG = "ClientFragment";private static boolean client_connect = false;private TextView client_edit;private Button connect_button;private TcpClientRunnable tcpClientRunnable;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_client, container, false);}@Overridepublic void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);client_edit = view.findViewById(R.id.client_edit);client_edit.setText(MyUtils.getLocalIp());connect_button = view.findViewById(R.id.connect_button);connect_button.setOnClickListener(this);tcpClientRunnable = new TcpClientRunnable();tcpClientRunnable.setPort(MyUtils.tcpSocketPort);tcpClientRunnable.setListener(tcpClientListener);}@Overridepublic void onClick(View view) {if (view.getId() == R.id.connect_button) {if (client_edit.getText().toString().trim().length() == 0) {Toast.makeText(getActivity(), "請輸入服務端IP", Toast.LENGTH_SHORT).show();} else {if (!client_connect) {client_connect = true;tcpClientRunnable.setIp(client_edit.getText().toString().trim());MyUtils.ExecuteRunnable(tcpClientRunnable);client_edit.setText(MyUtils.getLocalIp());client_edit.setEnabled(false);connect_button.setText("斷開");} else {client_connect = false;tcpClientRunnable.close();client_edit.setText(client_edit.getText().toString().trim());client_edit.setEnabled(true);connect_button.setText("連接");}}}}@Overridepublic void onDestroy() {super.onDestroy();tcpClientRunnable.close();}TcpClientRunnable.ClientListener tcpClientListener = new TcpClientRunnable.ClientListener() {@Overridepublic void onClientConnect() {Log.i(TAG,"TCP連接成功,跳轉DisplayActivity");Intent intent = new Intent(getActivity(), DisplayActivity.class);startActivity(intent);}@Overridepublic void onClientClose() {ToastUtils.showShort("TCP連接斷開!");}@Overridepublic void onClientReceiveBitmap(Bitmap bitmap, int width, int height) {MyUtils.setBitmap(bitmap, width, height);}};
}

至此,通過Socket實現的手機局域網投屏軟件完成。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/38682.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38682.shtml
英文地址,請注明出處:http://en.pswp.cn/news/38682.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Excel設置某列或者某行不某行不可以編輯,只讀屬性

設置單元格只讀的三種方式: 1、通過單元格只讀按鈕,設置為只為 設置行或者列的只讀屬性,可以設置整行或者整列只讀 2、設置單元格編輯控件為標簽控件(標簽控件不可編輯) 3、通過鎖定行,鎖定行的修改。鎖定的行與只讀行的區別在于鎖定的行不…

電子商務環境下旅游價值鏈

邁克爾 ? 波特(Michael E. Porter)在其《競爭優勢》一書中提出了“價值鏈” 的概念,并認為一家企業最核心的競爭優勢在于對價值鏈的設計。雖然邁克爾 ? 波 特提出的價值鏈主要是針對企業內部的價值鏈,但他視價值鏈為一系列連續完成的 活動&#xff…

openGauss學習筆記-40 openGauss 高級數據管理-鎖

文章目錄 openGauss學習筆記-40 openGauss 高級數據管理-鎖40.1 語法格式40.2 參數說明40.3 示例 openGauss學習筆記-40 openGauss 高級數據管理-鎖 如果需要保持數據庫數據的一致性,可以使用LOCK TABLE來阻止其他用戶修改表。 例如,一個應用需要保證表…

GPT垂直領域相關模型 現有的開源領域大模型

對于ToC端來說,廣大群眾的口味已經被ChatGPT給養叼了,市場基本上被ChatGPT吃的干干凈凈。雖然國內大廠在緊追不舍,但目前絕大多數都還在實行內測機制,大概率是不會廣泛開放的(畢竟,各大廠還是主盯ToB、ToG市…

C/C++ 注意點補充

C/C 注意點補充 函數缺省 函數缺省 https://blog.csdn.net/xinger_28/article/details/83898804 // 是的,C語言中的函數不支持直接定義缺省參數。在你提供的代碼中,函數DelayXms沒有定義缺省參數。缺省參數只在一些高級編程語言中(如C&…

flutter

1.dart語言學習 dart在線編輯器 //第一段dart代碼 void main() {ceshi c new ceshi(1,2);print(c.right);c.right 2;print(c.right);print(c.bottom);c.bottom 4;print(c.bottom); }class ceshi {num left, top;ceshi(this.left, this.top);num get right > left top;…

視頻集中存儲安防監控平臺EasyCVR優化AI硬件接入時的通道顯示異常問題

安防視頻監控平臺視頻集中存儲EasyCVR可拓展性強、視頻能力靈活、部署輕快,可支持的主流標準協議有國標GB28181、RTSP/Onvif、RTMP等,以及支持廠家私有協議與SDK接入,包括海康Ehome、海大宇等設備的SDK等。 安防監控視頻云存儲平臺EasyCVR既具…

【Python國內源】pip換源終極方法【Windows】

1、為什么要pip換源下載 安裝第三方庫時,很多庫來自于國外,下載速度慢得感人! 2、常見的國內源 https://pypi.tuna.tsinghua.edu.cn/simple #清華 http://mirrors.aliyun.com/pypi/simple/ #阿里云 https://pypi.mirrors.ustc.e…

go_細節注意

go細節 一、使用指針接受者和不使用指針接受者1,不使用指針接受者:2,使用指針接受者3,區別與優劣勢 一、使用指針接受者和不使用指針接受者 1,不使用指針接受者: func (d dog) move() {fmt.Println("…

使用Logstash將數據從MySQL同步至Elasticsearch(有坑)

文章目錄 一、準備工作1、安裝elasticSearchkibana2、安裝MySQL3、安裝Logstash 二、全量同步1、準備MySQL數據與表2、上傳mysql-connector-java.jar3、啟動Logstash4、修改logstash.conf文件5、修改full_jdbc.sql文件6、打開Kibana創建索引和映射7、重啟logstash進行全量同步8…

TCP/IP協議追層分析物理層(第三十九課)

TCP/IP協議追層分析物理層(第三十九課) 1 物理層:建立、維護、斷開物理連接,定義了接口及介質,實現了比特流的傳輸。 1、傳輸介質分類 有線介質:網線(雙絞線)、光纖 無線介質:無線電 微波 激光 紅外線 2、雙絞線分類: 五類cat5: 適用于100Mbps 超五類cat5e:適用于…

Qt掃盲- Graphics View框架理論綜述

Graphics View框架理論綜述 一、概述二、Graphics View 體系結構1. The Scene2. The View3. 圖元 Item 三、圖形視圖坐標系統1. 圖元Item的坐標2. Scene Scene坐標3. View 視圖坐標4. 坐標映射 四、關鍵特性1. 縮放和旋轉2. 打印3. 拖放4. 鼠標指針和 提示5. 動畫6. OpenGL渲染…

【100天精通python】Day35:一文掌握GUI界面編程基本操作

目錄 專欄導讀 1 GUI 編程概述 1.1 為什么需要GUI? 1.2 常見的GUI編程工具和庫 1.3 GUI應用程序的組成和架構 2 使用Tkinter 庫 進行GUI編程 2.1 使用Tkinter庫進行GUI編程的基本流程 2.2 使用Tkinter庫進行GUI編程 2.2.1 導入Tkinter庫 2.2.2 添加標簽和…

繪制世界地圖or中國地圖

寫在前面 在8月初,自己需要使用中國地圖的圖形,自己就此也查詢相關的教程,自己也做一下小小總結,希望對自己和同學們有所幫助。 最終圖形 這個系列從2022年開始,一直更新使用R語言分析數據及繪制精美圖形。小杜的生信筆記主要分享小杜學習日常!如果,你對此感興趣可以加…

Flutter Engine編譯環境安裝

前言 根據設置引擎開發環境的描述,確保有以下可用依賴項: Linux、macOS 或 Windows。 Linux 支持 Android 和 Fuchsia 的交叉編譯工件,但不支持 iOS。macOS 支持 Android 和 iOS 的交叉編譯工件。Windows 不支持任何 Android、Fuchsia 或 i…

MySQL存儲結構及索引

文章目錄 MySQL結構1.2存儲引擎介紹1.3存儲引擎特點InnoDB邏輯存儲結構 MyISAMMemory區別及特點存儲引擎選擇 索引索引概述索引結構BTreeHash索引分類聚集索引&二級索引索引語法SQL性能分析索引優化最左前綴法則范圍查詢字符串不加引號模糊查詢or連接條件數據分布影響覆蓋索…

達夢數據庫dbms_stats包的操作實踐記錄

索引的統計信息收集 GATHER_INDEX_STATSindex_stats_show 根據模式名,索引名獲得該索引的統計信息。用于經過 GATHER_TABLE_STATS、GATHER_INDEX_STATS 或 GATHER_SCHEMA_STATS 收集之后展示。返回兩個結果集:一個是索引的統計信息;另一個是…

Kotlin優點及為什么使用Kotlin

文章目錄 一 Hello Kotlin二 Kotlin優點三 團隊為什么采用 Kotlin 一 Hello Kotlin Kotlin和Andriod 二 Kotlin優點 三 團隊為什么采用 Kotlin

如何從PHP 獲取絕對路徑、文檔根目錄、基本 URL

根據您的服務器配置,獲取正確的路徑信息可能具有挑戰性。例如,PHP 并沒有直接提供一個變量來返回站點基本 URL。以下是一些代碼片段,可以幫助您獲取絕對路徑、文檔根目錄和基本 URL。 獲取絕對路徑 如果您的腳本位于 /path/directory/ 目錄中,您可以使用以下代碼片段來獲…

Mendix 基礎審計模塊介紹

一、前言 作為售前顧問,幫助客戶選型低代碼產品是日常工作。考察一家低代碼產品的好壞,其中一個維度就是產品的成熟度。產品成熟度直接影響產品在使用中的穩定性和用戶體驗,對于新工具導入和可持續運用至關重要。 那怎么考察一個產品是否成…