Wednesday, February 24, 2021

Segmentation fault

Segmentation fault,又譯為記憶體段錯誤,也稱存取權限衝突(access violation),是一種程式錯誤

它會出現在當程式企圖存取CPU無法定址的記憶體區段時。當錯誤發生時,硬體會通知作業系統產生了記憶體存取權限衝突的狀況。作業系統通常會產生核心轉儲(core dump)以方便程式員進行除錯。通常該錯誤是由於調用一個位址,而該位址為空(NULL)所造成的,例如連結串列中調用一個未分配位址的空鏈錶單元的元素。陣列存取越界也可能產生這個錯誤。

example 1


Linux环境下段错误的产生原因及调试方法小结



響應速度優化

响应速度优化的核心思想是 避免在主线程中做耗时操作 ,常见的就是IO操作以及计算量大的操作等。

Tuesday, February 23, 2021

內存優化

 1. 常见内存泄漏

造成内存泄漏的根本原因

生命周期较短的某个对象被生命周期更长的对象所持有,导致该对象不能及时释放。

1.1 静态变量导致的内存泄漏

因为静态变量生命周期等于应用程序的生命周期,所以静态变量引用的变量不会被回收掉。这里涉及到 GC Roots 的概念。

下面是两种明显的内存泄漏:

======================================================================

public class MyCouponActivity extends BaseActivity  {
    private static Context sContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_coupon);
        sContext = this;
    }
}
// or
public class MyCouponActivity extends BaseActivity  {
    private static View sView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_coupon);
        sView = new View(this);
    }
}

======================================================================

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 多线程引起的内存泄露

我们一般使用匿名类等来启动一个线程,如下:

======================================================================

new Thread(new Runnable() {
    @Override
    public void run() {
    }
}).start();

======================================================================

同样,匿名 Thread 类里持有了外部类的引用。当 Activity 退出时,Thread有可能还在后台执行,这时就会发生了内存泄露。

解决方案和上面 Handler 类似:要不就是变成

静态内部类,引用外面资源时使用WeakReference

要不就是在Activity退出时,结束线程。

1.3 其他情况造成的内存泄漏

集合类内存泄露

集合类添加元素后,将会持有元素对象的引用,导致该元素对象不能被垃圾回收,从而发生内存泄漏

 属性动画导致的内存泄漏

属性动画中有一类无限循环的动画,如果Activity中播放此类动画且没有在onDestory方法中去停止动画,那么动画会一直播放下去。我们需要在Activity#onDestory中调用animator.cancel()方法来停止动画。

 网络、文件等流忘记关闭

手动注册广播时,退出时忘记unregisterReceiver()

Service执行完后忘记stopSelf()

EventBus等观察者模式的框架忘记手动解除注册

2 内存管理


Android性能优化

佈局優化

繪制優化

onDraw()

內存優化

響應速度優化

ListView 優化

Bitmap 優化

Thread 優化

Reference:

https://blog.yorek.xyz/android/framework/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/

Android Proxy Pattern

 

1.定义

为其他对象提供一种代理以控制这个对象的访问。

2.介绍

  • 代理模式属于结构型模式。
  • 代理模式也叫委托模式。
  • 生活中,比如代购、打官司等等,实际上都是一种代理模式。
角色说明:
  • Subject(抽象主题类):接口或者抽象类,声明真实主题与代理的共同接口方法。
  • RealSubject(真实主题类):也叫做被代理类或被委托类,定义了代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接的调用真实主题类的方法。
  • Proxy(代理类):也叫委托类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
  • Client(客户端类):使用代理模式的地方。

4.实现

以海外代购为例,在国内的人想买国外的东西只能去找国外的人去进行代购。

4.1 创建抽象主题类

人都是有购买这个方法的:

=========================================================

    public interface People {
        void buy();//购买
    }

=========================================================

4.2 创建真实主题类

国内的人想购买某些产品,定义具体的购买过程:

=========================================================

    public class Domestic implements People {
        @Override
        public void buy() {//具体实现
            System.out.println("国内要买一个包");
        }
    }

=========================================================

4.3 创建代理类

海外的代购党需要知道是谁(持有真实主题类的引用)想购买啥产品:

=========================================================

     public class Oversea implements People {
        People mPeople;//持有People类的引用
        public Oversea(People people) {
            mPeople = people;
        }
        @Override
        public void buy() {
            System.out.println("我是海外代购:");
            mPeople.buy();//调用了被代理者的buy()方法,
        }
    }

=========================================================

4.4 客户端测试:
=========================================================
     public void test() {
        People domestic = new Domestic();        //创建国内购买人
        People oversea = new Oversea(domestic);  //创建海外代购类并将domestic作为构造函数传递
        oversea.buy();                           //调用海外代购的buy()
    }
=========================================================

输出结果:
=========================================================
我是海外代购:
国内要买一个包
=========================================================

5 静态代理与动态代理

从代码的角度来分,代理可以分为两种:一种是静态代理,另一种是动态代理。

静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。上面的例子实现就是静态代理。

动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

下面我们实现动态代理,Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法:

5.1 创建动态代理类
=========================================================
    public class DynamicProxy implements InvocationHandler {//实现InvocationHandler接口
        private Object obj;//被代理的对象

        public DynamicProxy(Object obj) {
            this.obj = obj;
        }

        //重写invoke()方法
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("海外动态代理调用方法: "+method.getName());
            Object result = method.invoke(obj, args);//调用被代理的对象的方法
            return result;
        }
    }
=========================================================

5.2 修改客户端的测试方法:
=========================================================
    public void test() {
        People domestic = new Domestic();                                 //创建国内购买人
        DynamicProxy proxy = new DynamicProxy(domestic);                  //创建动态代理
        ClassLoader classLoader = domestic.getClass().getClassLoader();   //获取ClassLoader
        People oversea = (People) Proxy.newProxyInstance(classLoader, new Class[]{People.class}, proxy); //通过 Proxy 创建海外代购实例 ,实际上通过反射来实现的。
        oversea.buy();//调用海外代购的buy()
    }
=========================================================
输出结果:
=========================================================
海外动态代理调用方法: buy
国内要买一个包
=========================================================

5.3 静态代理与动态代理比较

静态代理的缺点:

=========================================================

  • 静态代理如果接口新增一个方法,除了所有实现类(真实主题类)需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象。必须要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

  • =========================================================

    动态代理的优点:

    =========================================================
  • 可以通过一个代理类完成全部的代理功能,接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。当接口方法数量较多时,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
  • 动态代理的应用使我们的类职责更加单一,复用性更强。
  • =========================================================

    动态代理的缺点:

    =========================================================
    1. 不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,那么就不能使用这种方式进行动态代理(因为$Proxy()这个类集成了Proxy,Java的集成不允许出现多个父类)。
    =========================================================

    Reference:

    https://www.jianshu.com/p/a0e687e0904f

    Monday, February 22, 2021

    Android Facade Pattern

     

    1.定义

    要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易于使用。

    5. 应用场景

    • 为一个复杂的子系统提供一个简单接口,对外隐藏子系统的具体实现、隔离变化。
    • 使用外观模式可以将一个子系统和使用它的客户端以及其它的子系统分离开来,这就提高了子系统的独立性和可移植性。
    • 在构建一个层次化结构的时候,可以使用外观模式定义每一个层次对外交互的接口。这样,层与层之间只需要通过外观进行通信,从而简化层与层之间的依赖关系。

    6. 优点

    • 降低了客户端与子系统类的耦合度,实现了子系统与客户之间的松耦合关系。
    • 外观类对子系统的接口封装,使得系统更易于使用。
    • 提高灵活性,不管子系统如何变化,只要不影响门面对象,就可以自由修改。

    7. 缺点

    • 增加新的子系统可能需要修改外观类的源代码,违背了“开闭原则”。
    • 所有子系统的功能都通过一个接口来提供,这个接口可能会变得很复杂。

    8. Android中的源码分析

    外观模式在Android中应用也非常广泛,比如Context类,里面封装了很多方法,还是以startActivity()方法为例。

    实际上startActivity()是通过ActivityManagerService来实现的,ActivityManagerService我们应该都有耳闻,但是实际开发中一般都用不到,通过封装的方式,Context类隐藏了这些细节,我们只要简单调个方法就可以启动一个新的Activity
    这就是外观模式在Android应用的例子了。当然这种应用比比皆是,我们平时开发也经常用的到。

    Android Design Pattern Index

    创建型模式

    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



    Saturday, February 20, 2021

    typedef、struct、與 union

    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;
    };

    如果 struct 後面的 SCSI_CDB 不寫,則為一匿名 Struct,通常需搭配 typedef 或 union 使用。


    請留意位元組順序(Byte Order 或 Endianness),這會影響 2 Bytes 以上的格子分布情形。

    Bit Field

    如圖所示,使用 Struct 甚至可以切割出以 bit 為單位大小的格子,只要在變數名稱後面加上:以及所需 bit 數即可

    (注意:bit 數不能超過所宣告的型態,例如宣告一個 U8,最多就只能用 8 bits)。

    注意:這裡寫的都是參考值,如果情況允許編譯器會照做,但有時他會按自己喜歡的方式來決定每個欄位的大小(通常整體 Struct 大小會增加到 2的N次方 Bytes),想要強制對齊可以使用以下兩種方法:

    1. struct FOO {/*your struct fields*/}__attribute__((packed));
    2. #pragma pack(push1)
      // your structs
      #pragma pack(pop)

    搭配 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 index

     【n8n免費本地端部署】Windows版|程式安裝x指令大補帖  【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能