Segmentation fault,又譯為記憶體段錯誤,也稱存取權限衝突(access violation),是一種程式錯誤
它會出現在當程式企圖存取CPU無法定址的記憶體區段時。當錯誤發生時,硬體會通知作業系統產生了記憶體存取權限衝突的狀況。作業系統通常會產生核心轉儲(core dump)以方便程式員進行除錯。通常該錯誤是由於調用一個位址,而該位址為空(NULL)所造成的,例如連結串列中調用一個未分配位址的空鏈錶單元的元素。陣列存取越界也可能產生這個錯誤。
Segmentation fault,又譯為記憶體段錯誤,也稱存取權限衝突(access violation),是一種程式錯誤
它會出現在當程式企圖存取CPU無法定址的記憶體區段時。當錯誤發生時,硬體會通知作業系統產生了記憶體存取權限衝突的狀況。作業系統通常會產生核心轉儲(core dump)以方便程式員進行除錯。通常該錯誤是由於調用一個位址,而該位址為空(NULL)所造成的,例如連結串列中調用一個未分配位址的空鏈錶單元的元素。陣列存取越界也可能產生這個錯誤。
1. 常见内存泄漏
造成内存泄漏的根本原因
生命周期较短的某个对象被生命周期更长的对象所持有,导致该对象不能及时释放。
1.1 静态变量导致的内存泄漏
因为静态变量生命周期等于应用程序的生命周期,所以静态变量引用的变量不会被回收掉。这里涉及到 GC Roots 的概念。
下面是两种明显的内存泄漏:
======================================================================
======================================================================
1.2 非静态内部类(匿名类)内存泄露
注意一下静态匿名内部类和非静态匿名内部类的区别
非静态匿名内部类会持有外部class的强引用。
Handler 需要使用 static 修饰,且持有 Activity 时需要持有 WeakReference的缘故
1.2.1 HANDLER 内存泄漏
使用非静态内部类来实现Handler,lint就会给出警告。这涉及到Handler的原理。
如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
首先,非静态的Handler类会默认持有外部类的引用,包含Activity等
然后,还未处理完的消息(Message)中会持有Handler的引用
还未处理完的消息会处于消息队列中,即消息队列MessageQueue会持有Message的引用
消息队列MessageQueue位于Looper中,Looper的生命周期跟应用一致
因此,此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。所以,这时退出Activity的话,由于存在上述的引用关系,垃圾回收器将无法回收Activity,从而造成内存泄漏。
1.2.2 多线程引起的内存泄露
我们一般使用匿名类等来启动一个线程,如下:
======================================================================
======================================================================
同样,匿名 Thread 类里持有了外部类的引用。当 Activity 退出时,Thread有可能还在后台执行,这时就会发生了内存泄露。
解决方案和上面 Handler 类似:要不就是变成
静态内部类,引用外面资源时使用WeakReference
要不就是在Activity退出时,结束线程。
1.3 其他情况造成的内存泄漏
集合类内存泄露
集合类添加元素后,将会持有元素对象的引用,导致该元素对象不能被垃圾回收,从而发生内存泄漏
属性动画导致的内存泄漏
属性动画中有一类无限循环的动画,如果Activity中播放此类动画且没有在onDestory方法中去停止动画,那么动画会一直播放下去。我们需要在Activity#onDestory中调用animator.cancel()方法来停止动画。
网络、文件等流忘记关闭
手动注册广播时,退出时忘记unregisterReceiver()
Service执行完后忘记stopSelf()
EventBus等观察者模式的框架忘记手动解除注册
2 内存管理
繪制優化
onDraw()
ListView 優化
Bitmap 優化
Thread 優化
Reference:
https://blog.yorek.xyz/android/framework/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/
为其他对象提供一种代理以控制这个对象的访问。
以海外代购为例,在国内的人想买国外的东西只能去找国外的人去进行代购。
人都是有购买这个方法的:
=========================================================
=========================================================
国内的人想购买某些产品,定义具体的购买过程:
=========================================================
=========================================================
海外的代购党需要知道是谁(持有真实主题类的引用)想购买啥产品:
=========================================================
=========================================================
静态代理的缺点:
=========================================================
InvocationHandler.invoke
)。当接口方法数量较多时,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。Reference:
https://www.jianshu.com/p/a0e687e0904f
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易于使用。
外观模式在Android中应用也非常广泛,比如Context
类,里面封装了很多方法,还是以startActivity()
方法为例。
实际上startActivity()
是通过ActivityManagerService
来实现的,ActivityManagerService
我们应该都有耳闻,但是实际开发中一般都用不到,通过封装的方式,Context
类隐藏了这些细节,我们只要简单调个方法就可以启动一个新的Activity
。
这就是外观模式在Android应用的例子了。当然这种应用比比皆是,我们平时开发也经常用的到。
创建型模式
Singleton Pattern (确保某一个类只有一个实例,并且提供一个全局访问点)
Builder Pattern (用来创建复杂的复合对象)
Prototype Pattern (通过复制原型来创建新对象)
Simple Pattern (确定只有一个工厂类,可以使用简单工厂模式)
Factory Pattern (让子类来决定要创建哪个对象)
Abstract Factory Pattern (创建多个产品族中的产品对象)
行为型模式
Strategy Pattern (封装不同的算法,算法之间能互相替换)
Status Pattern (根据不同的状态做出不同的行为)
Chain of Responsibility Pattern (将事件沿着链去处理)
Observer Pattern (状态发生改变时通知观察者,一对多的关系)
Template Method (定义一套流程模板,根据需要实现模板中的操作)
Iterator Pattern (提供一种方法顺序访问一个聚合对象中的各个元素)
Memento Pattern (保存对象的状态,在需要时进行恢复)
Visitor Pattern (稳定数据结构中,定义新的操作行为)
Mediator Pattern (将网状结构转变为星型结构,所有行为都通过中介)
Interpreter Pattern (定义语法,并对其进行解释)
Command Pattern (将请求封装成命令,并记录下来,能够撤销与重做)
结构型模式
Proxy Pattern (控制客户端对对象的访问)
Composite Pattern (将整体与局部(树形结构)进行递归组合,让客户端能够以一种的方式对其进行处理)
Adapter Pattern (将原来不兼容的两个类融合在一起)
Decorator Pattern (为对象添加新功能)
Flyweight Pattern (使用对象池来减少重复对象的创)
Facade Pattern (对外提供一个统一的接口用来访问子系统)
Bridge Pattern (将两个能够独立变化的部分分离开来)
Reference:
https://juejin.cn/post/6844903437700710408#heading-0
https://www.jianshu.com/p/9a480322aee1
union 避免重複代碼,又能避免耦合,同時還能節約資源呢
]typedef
這個絕對是節省打字數的一大幫手。例如定義一個 unsigned char 為 U8 型態:
typedef unsigned int U8, *U8_PTR; //這樣寫U8_PTR就會是「U8指標」型態
typedef U8* MyFunc_PTR(U8, U8); //此函式return一個「U8的指標(就是U8_PTR)」
typedef U8 (*MyFunc_PTR)(U8, U8); //此函式return一個「U8」數值
U8 * num_ptr1, num_ptr2; //此時num_ptr2是普通的U8而非指標
U8_PTR num_ptr1, num_ptr2; //確保num_ptr1、num_ptr2皆為U8_PTR
struct
C 語言跟某一區段記憶體區塊的處理息息相關,例如開一個陣列 Array 將一連續記憶體空間切割成相同大小;
而 Struct 則是用來將一連續記憶體空間切割成大大小小的命名區塊。
舉例定義一個新形態命名為 SCSI_CDB:
struct SCSI_CDB
{
BYTE opc;
BYTE evpd : 1; // occupy 1 bit
BYTE cmddt : 1; // occupy 1 bit
BYTE rsv : 6; // occupy 6 bits
BYTE page_code;
WORD alloc_len;
BYTE control;
};
請留意位元組順序(Byte Order 或 Endianness),這會影響 2 Bytes 以上的格子分布情形。
Bit Field
如圖所示,使用 Struct 甚至可以切割出以 bit 為單位大小的格子,只要在變數名稱後面加上:以及所需 bit 數即可
(注意:bit 數不能超過所宣告的型態,例如宣告一個 U8,最多就只能用 8 bits)。
注意:這裡寫的都是參考值,如果情況允許編譯器會照做,但有時他會按自己喜歡的方式來決定每個欄位的大小(通常整體 Struct 大小會增加到 2的N次方 Bytes),想要強制對齊可以使用以下兩種方法:
搭配 typedef
改寫同樣 Struct。
typedef struct _SCSI_CDB
{ /* your struct fields */
// ...
} SCSI_CDB, *SCSI_CDB_PTR;
此處_SCSI_CDB可寫可不寫,僅供辨識用。
union
在同一連續記憶體空間,想要在不同時候採用不同切法(一般型態、Array 或 Struct 皆可)時就可以使用 union,其所佔的實際大小(即 sizeof 的值)將由最肥的那一組來決定。
舉例定義一個新形態命名為SCSI_CDB:
union SCSI_CDB
{
struct {
BYTE opc;
BYTE evpd : 1;
BYTE cmddt : 1;
BYTE rsv : 6;
BYTE page_code;
WORD alloc_len;
BYTE control;
} inquiry; // 6 bytes
struct {
BYTE opc;
BYTE obs : 2;
BYTE rarc : 1;
BYTE fua : 1;
BYTE dpo : 1;
BYTE rd_protect : 3;
DWORD slba;
BYTE grp : 5;
BYTE rsv : 3;
WORD lba_len;
BYTE control;
} read10; // 10 bytes
};
此例中 SCSI_CDB 這個 Union 結構的大小就是 10 Bytes,其中前 6 個 Byte 會被兩個匿名 Struct 所共用。覺得一個 Union 寫起來太長可以把各 Struct 拆出來寫變成這樣
struct SCSI_INQUIRY {/* ... */};
struct SCSI_READ10 {/* ... */};
union SCSI_CDB
{
SCSI_INQUIRY inquiry; // 6 bytes
SCSI_READ10 read10; // 10 bytes
};
當然,也可以混用其他型態隨你玩。
union SCSI_CDB
{
long long dummy;
U8 bCDB[16];
struct {
BYTE opc;
BYTE rsv;
BYTE page_code;
WORD alloc_len;
BYTE control;
} inquiry; // 6 bytes
};
搭配 typedef
改寫同樣 Union。
typedef struct _SCSI_INQUIRY {/* ... */} SCSI_INQUIRY, *SCSI_INQUIRY_PTR;
typedef struct _SCSI_READ10 {/* ... */} SCSI_READ10, *SCSI_READ10_PTR;
typedef union _SCSI_CDB
{
SCSI_INQUIRY inquiry; // 6 bytes
SCSI_READ10 read10; // 10 bytes
} SCSI_CDB, *SCSI_CDB_PTR;
此處_SCSI_INQUIRY、_SCSI_READ10、及_SCSI_CDB皆可寫可不寫,僅供辨識用。
改善範例:以填 SCSI CDB 為例
在傳送一些 SCSI Command 的時候,原本的 Code 都是直接填 CDB 格子,但總覺得不夠直觀,於是紀錄一下可行的改善法。
Before
從 SCSI Command 的 Sample Code 中可以簡單這樣使用,例如一個 Inquiry、及一個 Read(10)。
// Inquiry
unsigned char cdb[6] = {0};
cdb[0] = 0x12; // opcode
cdb[2] = 0; // page_code
cdb[3] = (512 >> 8) & 0xFF; // Upper bytes of allocate_length
cdb[4] = (512) & 0xFF; // Lower bytes of allocate_length
// Read
unsigned char cdb[10] = {0};
cdb[0] = 0x28; // opcode
cdb[2] = (2048 >> 32) & 0xFF; // MSB of starting_lba
cdb[3] = (2048 >> 24) & 0xFF;
cdb[4] = (2048 >> 16) & 0xFF;
cdb[5] = (2048 >> 0) & 0xFF; // LSB of starting_lba
cdb[7] = (1 >> 8) & 0xFF; // Upper bytes of read_length
cdb[8] = (1) & 0xFF; // Lower bytes of read_length
根據 Spec 可以知道每個 Command 所對應到的同一個位子有不同的意思,甚至長度也不同。這樣一來就很麻煩,變成每次要填格子都要看一次 Spec,而且寫出來的 Code 也難以理解。
After
於是改造一下可以寫成這個樣子,應該是容易理解許多。一樣一個 Inquiry、及一個 Read(10)。
// Inquiry
SCSI_CDB cdb = {0};
cdb.inquiry.opc = 0x12;
cdb.inquiry.page_code = 0;
cdb.inquiry.alloc_len = SWAP_U16(512); // Allocate 512 bytes for inquiry data
// Read10
SCSI_CDB cdb = {0};
cdb.read10.opc = 0x28;
cdb.read10.slba = SWAP_U32(2048); // Starting LBA = 2048
cdb.read10.lba_len = SWAP_U16(1); // Read Length = 1 LBA
註:SWAP_16 及 SWAP_32 用到 #define 以更改 Byte Order 來符合 Spec 要求。
欸你說這樣還不是要記得有哪些 Command、還有該 Command 對應哪些欄位?等等,其實只要先進一點的文字編輯器帶有 Intillisense 自動完成功能,這個問題就不是問題,總比在那邊翻 Spec 來的容易。
【n8n免費本地端部署】Windows版|程式安裝x指令大補帖 【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能