主頁 > 前端設計 > 淺談IPC|Binder

淺談IPC|Binder

2020-09-13 16:01:16 前端設計

前言

  • IPC是什么?
  • 為什么要學IPC
  • 怎么進行IPC

下面是我這篇博文的學習腦圖,方便讀者更快的找到自己想要了解的知識點,

一、IPC簡介

1.1什么是IPC

IPC是Inter-Process Communication的縮寫,含義為行程間通信或者跨行程通信,是指兩個行程之間進行資料交換的程序

看到這里,需要先了解一下行程、執行緒以及它們的關系,

  • 行程:一般指一個執行單元,在PC和移動設備上指一個程式應用

  • 執行緒:CPU調度的最小單元,執行緒是一種有限的系統資源,

    二者之間的關系:

    一個行程可包含多個執行緒,即一個應用程式上可以同時執行多個任務,

    注意:不可在主執行緒做大量耗時操作,會導致ANR(應用無回應),

IPC不是Android中所獨有的,任何一個作業系統都需要有相應的IPC機制,Binder是Android中最有特色的行程間通信方式,

1.2為什么要進行IPC

行程間通信的必要性

? 所有運行在不同行程的四大組件,只要它們之間需要通過記憶體在共享資料,都會共享失敗,這是由于Android為每個應用分配了獨立的虛擬機,不同的虛擬機在記憶體分配上有不同的地址空間,這會導致在不同的虛擬機中訪問同一個類的物件會產生多份副本,

1.3怎么進行IPC

使用Bundle、檔案共享、MessengerAIDLContentProvider、Socket,

二、Android 中的多行程模式

2.1開啟行程

  • (常規)在AndroidMenifest中給四大組件指定屬性android:process
  • (不常規)通過JNI在native層fork一個新的行程,

行程名的默認規則:

默認行程:

  • 沒有指定該屬性則運行在默認行程,其行程名就是包名

以“:”開頭的行程:

  • 省略包名,如android:process=":remote",表示行程名為com.example.myapplication:remote
  • 屬于當前應用的私有行程,其他行程的組件不能和他跑在同一行程中,

完整命名的行程:

  • android:process="com.example.myapplication.remote"
  • 屬于全域行程,其他應用可以通過**ShareUID**方式和他跑在用一個行程中,

UID: Android系統會為每個應用分配一個唯一的UID,具有相同的UID才能共享資料,

兩個應用通過ShareUID跑在同一個行程中的條件:具有相同的ShareUID和簽名,

  • 滿足上述條件的兩個應用,無論是否跑在同一行程,它們可共享data目錄,組件資訊,
  • 若跑在同一行程,它們除了可共享data目錄、組件資訊,還可共享記憶體資料,它們就像是一個應用的兩個部分,

2.2多行程的運行機制

Android為每一個行程分配了一個獨立的虛擬機,不同虛擬機在記憶體分配上有不同的地址空間,這也導致了不同虛擬機中訪問同一個物件會產生多份副本

一般來說使用多行程會帶來以下四個方面的問題:

  • 靜態變數和單例模式失效 原因:不同虛擬機中訪問同一個物件會產生多份副本

  • 執行緒同步機制失效 原因:記憶體不同,執行緒無法同步,

  • SharedPreference的可靠性下降 原因:底層是通過讀寫XML檔案實作的,發生并發問題,

  • Application多次創建- 原因:Android系統會為新的行程分配獨立虛擬機,相當于應用重新啟動了一次,

為了解決這些問題,可以采用跨行程通信方法,通過Intent,共享檔案和SharedPreferencesMessengerAIDLSocket等,

三、IPC基礎概念

在了解以下三種介面使用前,需要先了解一下什么是序列化和反序列化,

1.什么是序列化?

含義:序列化表示將一個物件轉換成可存盤或可傳輸的狀態,序列化后的物件可以在網路上進行傳輸,也可以存盤到本地,

場景:需要通過IntentBinder等傳輸類物件就必須完成物件的序列化程序,

兩種方式:實作Serializable/Parcelable介面,

2.什么是反序列化?

把位元組序列恢復為物件的程序稱為物件的反序列化,與序列化相反,

3.1 Serializable介面

Serializable是Java所提供的一個序列化介面,是一個空介面,為物件提供標準的序列化和反序列化操作,

  • 用法:物體類實作Serializable介面,宣告serialVesionUID

  • 注意:serialVesionUID非必需,但是不宣告會對反序列化有影響,

? serialVesionUID與當前類的serialVesionUID相同才能被正常反序列化,

? serialVesionUID可以系統配置/手動修改,

代碼示例:

//User物體類實作Serializable介面;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    public int UserId;
    public String userName;
    public boolean isMale;

    public User(int userId, String userName, boolean isMale) {
        UserId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public int getUserId() {
        return UserId;
    }

    public void setUserId(int userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }
}
 //序列化程序
    User user = new User(1,"Yuki",true);
    ObjectOutputStream outputStream;

    {
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
    //反序列化程序
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));

            User newUser = (User)in.readObject();
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

3.2 Parcelable介面

用法:

  • 物體類實作Parcelable介面

  • 內容描述

  • 序列化

  • 反序列化

代碼示例:

//User物體類實作Parcelable;
public class User implements Parcelable {
    public int UserId;
    public String userName;
    public boolean isMale;
//    public Book book;

    public User(int userId, String userName, boolean isMale) {
        UserId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    protected User(Parcel in) {
        UserId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
//        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }
    

    //反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };




    public int getUserId() {
        return UserId;
    }

    public void setUserId(int userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }
    //內容描述
    @Override
    public int describeContents() {
        return 0;
    }
    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(UserId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
//        dest.writeParcelable(book,0);

    }
}

Parcelable方法說明

方法功能標記位
createFromParcel(Parcel in)從序列化后的物件中創建原始物件
newArray(int size)創建指定長度的原始物件陣列
writeToParcel(Parcel dest, int flags)將當前物件寫入序列化結構中,其中flags標識有兩種值:0或者1,為1時標識當前物件需要作為回傳值回傳,不能立即釋放資源,幾乎所有的情況都為0PARCELABLE_WRITE_RETURN_VALUE
User(Parcel in)從序列化后的物件中創建原始物件
describeContents回傳當前物件的內容描述,如果含有檔案描述符,回傳1,否則回傳0,幾乎所有的情況都回傳0CONTENTS_FILE_DESCRIPTOR

SerializableParcelable比較

SerializableParcelable
Java序列化介面Android序列化介面
使用簡單使用較麻煩
效率低效率高

3.3 Binder

3.3.1 Binder是什么?

  • 直觀上:Android 的一個類,實作了IBinder介面,
  • **IPC 角度:Android中的一種跨行程通信**,
  • 實作方式角度:一種虛擬的物理設備,
  • Android Framework角度::ServiceManager連接各種ManagerActivityManagerWindowManager等)的橋梁,
  • Android應用層角度:客戶端和服務端進行通信的媒介,

3.3.2 為什么是Binder?

Android系統是基于Linux內核的,Linux已經提供了管道、訊息佇列、記憶體共享和SocketIPC機制,為什么Android還要提供Binder來實作IPC

優勢描述
性能只需要一次資料拷貝,性能上僅次于共享記憶體
穩定性基于C/S架構,職責明確,架構清晰,穩定性好
安全性為每個APP分配UID,行程的UID事鑒別行程身份的重要標志

3.3.3 Binder IPC底層通信原理

3.3.3.1.其他IPC機制完成一次行程間通信是怎么樣的?

? 在不同的行程之間,訊息發送方將要發送的資料存放在記憶體快取區中,通過系統呼叫進入內核態,然后內核程式在內核空間分配記憶體,開辟一塊內核快取區,呼叫 copy*from*user() 函式將資料從用戶空間的記憶體快取區拷貝到內核空間的內核快取區中,同樣的,接收方行程在接收資料時在自己的用戶空間開辟一塊記憶體快取區,然后內核程式呼叫 copy*to*user() 函式將資料從內核快取區拷貝到接收行程的記憶體快取區,

3.3.3.2 Binder IPC完成一次行程間通信又是怎么樣的?
  1. 首先 Binder 驅動在內核空間創建一個資料接收快取區;
  2. 接著在內核空間開辟一塊內核快取區,建立內核快取區內核中資料接收快取區之間的映射關系,以及內核中資料接收快取區接收行程用戶空間地址的映射關系;
  3. 發送方行程通過系統呼叫 copy*from*user() 將資料 copy 到內核中的內核快取區,由于內核快取區和接收行程的用戶空間存在記憶體映射,因此也就相當于把資料發送到了接收行程的用戶空間,這樣便完成了一次行程間的通信,

各種IPC方式資料拷貝次數

IPC資料拷貝次數
共享記憶體0
Binder1
訊息佇列/管道/Socket2

3.3.4 Binder通信程序

Binder框架定義了四個角色:ServerClientServiceManager以及Binder驅動,其中ServerClientServiceManager運行于用戶空間,驅動運行于內核空間,這四個角色的關系和互聯網類似:Server是服務器,Client是客戶終端,ServiceManager是域名服務器(DNS),驅動是路由器,

  1. 首先,一個行程使用 BINDER*SET*CONTEXT_MGR 命令通過 Binder 驅動將自己注冊成為 ServiceManager

  2. Server 通過驅動向 ServiceManager 中注冊 BinderServer 中的 Binder 物體),表明可以對外提供服務,驅動為這個 Binder 創建位于內核中的物體節點以及 ServiceManager 對物體的參考,將名字以及新建的參考打包傳給 ServiceManagerServiceManger 將其填入查找表,

  3. Client 通過名字,在 Binder 驅動的幫助下從 ServiceManager 中獲取到對 Binder 物體的參考,通過這個參考就能實作和 Server 行程的通信,

    這里參考自:寫給 Android 應用工程師的 Binder 原理剖析

問:當服務端行程例外終止的話,造成Binder死亡的話,怎么辦?

在客戶端系結遠程服務成功后,給Binder設定死亡代理,當Binder死亡的時候,我們會收到通知,從而重新發起連接請求,

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這里重新系結遠程Service
}
}
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

四、Android中的IPC模式

4.1 Bundle

概念:由于Bundle實作了Parcelable介面,可以方便的在不同行程間傳輸,

  • 范圍:ActivityServiceReceiver間傳遞,
  • 使用:通過Intent發送,

擴展使用:A行程要啟動B行程并把在A行程計算完的資料傳遞給B行程,如何把不支持Bundle的資料由A行程傳入B中?

答:將原本在A行程的計算任務轉移到B行程的后臺Service中去執行,通過Intent啟動B行程的一個Service,讓計算任務在Service完成,計算完成 后再去啟動目標組件,并把資料傳遞給目標組件,

4.2 檔案共享

概念:兩個行程通過讀/寫同一個檔案來交換資料,

  • 檔案范圍:對檔案格式沒有具體要求,
  • 局限性:并發讀/寫,

4.3 Messenger

概念:可以在不同行程中傳遞Message物件,把需要傳遞的資料放進物件中,

  • 底層實作:輕量級的 IPC 方案,它的底層實作是 AIDL

  • 實作Message

1.服務端行程

  • 創建一個Service處理客戶端連接請求
  • 創建一個Handle并通過它創建一個Messenger物件
  • ServiceonBind回傳這個物件的底層Binder

2.客戶端行程

  • 系結服務端的Service
  • 用服務端回傳的IBinder物件創建一個Messenger(客戶端——>服務端)
  • 在客戶端創建一個Handler并由此創建一個Messenger,并通過Message的**replyTo欄位**傳遞給服務器端行程,服務端通過讀取Message得到Messenger物件,進而向客戶端行程傳遞資料,(客戶端 <——>服務端)
//Messenger 服務端代碼
public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    private final Messenger messenger = new Messenger(new MessengerHandler());
    //處理客戶端發送的訊息
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                   case 1:
                    Log.i("TAG"," "+msg.getData().getString("msg"));
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    }
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();    //回傳它的Binder物件
    }
}
 <service android:name=".MessengerService"
            android:process=":remote"/>   //注冊Service
//客戶端代碼
public class MessengerActivity extends AppCompatActivity {

    private Messenger mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //系結服務
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service); //用服務端回傳的IBinder物件創建一個Messenger物件
            Message msg = Message.obtain(null,1);
            Bundle data = new Bundle();
            data.putString("msg","Client");
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

4.4 使用AIDL

AIDLAndroid Interface Definition Language 的縮寫,意思是Android介面定義語言,用于讓某個Service與多個應用程式組件之間進行跨行程通信,從而可以實作多個應用程式共享同一個Service的功能,其使用可以簡單的概括為服務端和客戶端,類似于Socket 一樣,服務端服務于所有客戶端,支持一對多服務,

4.4.1 MessengerAIDL比較

Messenger缺點:串行方式處理訊息,無法并發處理,

AIDL:可以并發處理請求,

AIDL通信流程

  • 服務端
  • 創建Service監聽客戶端請求
  • 創建AIDL檔案
  • AIDL檔案中申明暴露給客戶端的介面
  • Service實作這個AIDL介面
  • 客戶端
  • 系結服務端的Service
  • 將服務端回傳的Binder物件轉成AIDL介面所屬的型別
  • 呼叫AIDL方法

4.4.2AIDL能夠支持哪些資料型別?

注意:除了基本資料型別,其它型別的引數必須標上方向:in、out或inout,用于表示在跨行程通信中資料的流向,

4.4.3 關鍵類和關鍵方法

  • AIDL介面:繼承IInterface
  • StubBinder的實作類,服務端通過這個類來提供服務,
  • Proxy:服務器的本地代理,客戶端通過這個類呼叫服務器的方法,
  • asInterface():客戶端呼叫,將服務端的回傳的Binder物件,轉換成客戶端所需要的AIDL介面型別物件,

回傳物件:

  • 若客戶端和服務端位于同一行程,則直接回傳Stub物件本身;
  • 否則,回傳的是系統封裝后的Stub.proxy物件,
  • asBinder():回傳代理ProxyBinder物件,
  • onTransact():運行服務端的Binder執行緒池中,當客戶端發起跨行程請求時,遠程請求會通過系統底層封裝后交由此方法來處理,
  • transact():運行在客戶端,當客戶端發起遠程請求的同時將當前執行緒掛起,之后呼叫服務端的onTransact()直到遠程請求回傳,當前執行緒才繼續執行,

4.4.4 產生ANR的情形

  • 客戶端:

    • 呼叫服務端的方法是運行在服務端的Binder執行緒池中,若主執行緒所呼叫的方法里執行了較耗時的任務,同時會導致客戶端執行緒長時間阻塞,易導致客戶端ANR
    • onServiceConnected()onServiceDisconnected()里直接呼叫服務端的耗時方法,易導致客戶端ANR
  • 服務端:

    • 服務端的方法本身就運行在服務端的**Binder執行緒中,可在其中執行耗時操作,而無需再開啟子執行緒**,
    • 回呼客戶端Listener的方法是運行在客戶端的Binder執行緒中,若所呼叫的方法里執行了較耗時的任務,易導致服務端ANR

解決客戶端頻繁呼叫服務器方法導致性能極大損耗的辦法:實作觀察者模式

即當客戶端關注的資料發生變化時,再讓服務端通知客戶端去做相應的業務處理,

4.4.5解注冊失敗的問題

  • 原因: Binder進行物件傳輸實際是通過序列化和反序列化進行,即Binder會把客戶端傳遞過來的物件重新轉化并生成一個新的物件,雖然在注冊和解注冊的程序中使用的是同一個客戶端傳遞的物件,但經過Binder傳到服務端后會生成兩個不同的物件,另外,多次跨行程傳輸的同一個客戶端物件會在服務端生成不同的物件,但它們在底層的Binder物件是相同的,
  • 解決辦法:當客戶端解注冊的時候,遍歷服務端所有的Listener,找到和解注冊Listener具有相同的Binder物件的服務端Listener,刪掉即可,

需要用到RemoteCallBackListAndroid系統專門提供的用于洗掉跨行程listener的介面,其內部自動實作了執行緒同步的功能,

4.5 使用ContentProvider

4.5.1 什么是ContentProvider

ContentProviderAndroid中提供的專門用于不同應用間進行資料共享的方法,它天生就適合行程間通信,

4.5.2 如何自定義一個ContentProvider

  • 用一個物體類繼承ContentProvider
  • 實作onCreatequeryupdateinsertdeletegetType等六種抽象方法,、

注意:

除了onCreate()運行在UI執行緒中,其他的query()update()insert()delete()getType()都運行在Binder執行緒池中,

CRUD四大操作存在多執行緒并發訪問,要注意在方法內部要做好執行緒同步,

一個SQLiteDatabase內部對資料庫的操作有同步處理,但多個SQLiteDatabase之間無法同步,

4.6 使用Socket

4.6.1 什么是Socket

Socket也稱為“套接字”,是網路通信的概念,分為流式套接字和用戶資料報套接字兩種,

  • 流套接字:基于TCP協議,采用流的方式提供可靠的位元組流服務,
  • 資料流套接字:基于UDP協議,采用資料報文提供資料打包發送的服務,

4.6.2 怎么實作Socket通信?

  • 服務端:

    • 創建一個Service,在執行緒中建立TCP服務、監聽相應的埠等待客戶端連接請求;
    • 與客戶端連接時,會生成新的Socket物件,利用它可與客戶端進行資料傳輸;
    • 與客戶端斷開連接時,關閉相應的Socket并結束執行緒,
  • 客戶端:

    • 開啟一個執行緒、通過Socket發出連接請求;
    • 連接成功后,讀取服務端訊息;
    • 斷開連接,關閉Socket

五、IPC方式

各種IPC方式的優缺點比較:

名稱優點缺點適用場景
Bundle簡單易用只能傳輸Bundle支持的資料型別四大組件間的行程間通信
檔案共享簡單易用不適合高并發場景,無法做到行程間的即時通信無并發訪問,交換簡單資料且實時性不高
AIDL支持一對多并發和實時通信使用稍復雜,需要處理執行緒同步一對多且有RPC需求
Messenger支持一對多串行通信不能很好處理高并發,不支持RPC,只能傳輸Bundle支持的資料型別低并發的一對多
ContentProvider支持一對多并發資料共享可理解為受約束的AIDL一對多行程間資料共享
Socket支持一對多并發資料共享實作細節繁瑣網路資料交換

六、Binder連接池

6.1 典型的AIDL使用流程

  • 創建一個Service和一個AIDL介面
  • 創建一個類繼承自AIDL介面中的Stub類并實作Stub中的抽象方法
  • 在Service的onBind方法中回傳這個類的物件

6.2 當有多個不同的業務需要使用AIDL來進行通信,該怎么處理?

可以將所有的AIDL放在同一個Service中去管理,它的作業機制是這樣的:

每個業務模塊創建自己的AIDL介面,向服務端提供自己的唯一標識和對應的Binder物件,對服務端來說,只需一個Service,服務端提供一個queryBinder介面,可以根據業務模塊特征回傳相應的Binder物件,Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service去執行,避免重復創建Service

實作方式:

  • 為每個業務模塊創建AIDL介面并具體實作,
  • Binder連接池創建AIDL介面IBinderPool.aidl并具體實作,
  • 遠程服務BinderPoolService的實作,在onBind()回傳實體化的IBinderPool實作類物件,
  • Binder連接池的具體實作,來系結遠程服務,
  • 客戶端的呼叫,

本文參考:

  • 寫給 Android 應用工程師的 Binder 原理剖析

  • 《Android開發藝術探索》

  • 進階之路 | 奇妙的 IPC 之旅

  • Android Bander設計與實作 - 設計篇

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/25602.html

標籤:其他

上一篇:鴻蒙開發TV軟體環境搭建以及簡單教程

下一篇:安裝下載geckodriver

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more