1. 程式人生 > >深入解析開源專案之Universal-Image-Loader(二)快取篇

深入解析開源專案之Universal-Image-Loader(二)快取篇

Universal-Image-Loader 是一個優秀的圖片載入開源專案,Github地址在 (Github地址) ,很多童鞋都在自己的專案中用到了。優秀的專案從來都是把簡單留給開發者,把複雜封裝在框架內部。ImageLoader作為Github上Star數過萬的專案,備受開發者青睞,所以我們有必要搞清楚它的內部實現。

在上一篇部落格中我們分析了ImageLoader框架的整體實現原理,還沒有看過的直接到 深入解析開源專案之ImageLoader(一)框架篇

ImageLoader快取之記憶體篇

MemeryCache關係圖

由上圖我們可以看出:

  1. MemoryCache介面定義了Bitmap快取相關操作;

  2. 抽象類BaseMemoryCache中則使用HashMap儲存了Bitmap的軟/弱引用;

  3. LimitedMemoryCache類則使用LinkedList儲存了Bitmap強引用,並加入了對最大可用記憶體的限定。

BaseMemoryCache中的程式碼很簡單,只是簡單的將Bimtap從儲存軟/弱引用的HashMap中新增或移除。

接下來我們看看LimitedMemoryCache中是怎樣實現將Bitmap快取在記憶體中的:

/* LimitedMemoryCache.java */

    public boolean put(String key, Bitmap value
) { //這個欄位表示是否新增到強引用成功 boolean putSuccessfully = false; int valueSize = getSize(value); int sizeLimit = getSizeLimit(); int curCacheSize = cacheSize.get(); //如果Bitmap尺寸沒超過限定尺寸 if (valueSize < sizeLimit) { while (curCacheSize + valueSize > sizeLimit) { //如果新增Bitmap後超過了限定尺寸,根據抽象方
//法removeNext()移除強引用列表中的Bitmap, //直到小於限定尺寸 Bitmap removedValue = removeNext(); if (hardCache.remove(removedValue)) { curCacheSize = cacheSize.addAndGet(-getSize(removedValue)); } } //新增到強引用的列表中 hardCache.add(value); cacheSize.addAndGet(valueSize); putSuccessfully = true; } //新增到軟/弱引用中 super.put(key, value); return putSuccessfully; }

在看看LimitedMemoryCache的移除操作:

/* LimitedMemoryCache.java */

    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        if (value != null) {

            //軟/弱引用存在,移除強引用
            if (hardCache.remove(value)) {
                cacheSize.addAndGet(-getSize(value));
            }
        }

        //移除軟/弱引用
        return super.remove(key);
    }

再看看/cache/memory/impl實現中,分為三類:

實現MemeryCache 繼承LimitedMemoryCache 繼承BaseMemoryCache
FuzzyKeyMemoryCache FIFOLimitedMemoryCache WeakMemoryCache
LimitedAgeMemoryCache LargestLimitedMemoryCache
LruMemoryCache LRULimitedMemoryCache
UsingFreqLimitedMemoryCache

下面逐一對上面的實現分析:

實現MemeryCache

FuzzyKeyMemoryCache

用到了裝飾模式,對MemoryCache的put(String key, Bitmap value)方法進行加強處理:先移除key相同的Bitmap,再新增新的key對應的Bitmap:

/* FuzzyKeyMemoryCache.java */

    public boolean put(String key, Bitmap value) {
        synchronized (cache) {
            String keyToRemove = null;
            for (String cacheKey : cache.keys()) {
                if (keyComparator.compare(key, cacheKey) == 0) {
                    //找到Uri相同的
                    keyToRemove = cacheKey;
                    break;
                }
            }

            //先移除Uri相同的Cache
            if (keyToRemove != null) {
                cache.remove(keyToRemove);
            }
        }

        //再將新的key對應的值放入Cache
        return cache.put(key, value);
    }

LimitedAgeMemoryCache

也是對MemoryCache的裝飾,對MemeryCache的get(String key)做了加強處理:當我們在獲取記憶體Cache中的Bitmap時,如果超過最大存活時間則不在返回

/* LimitedAgeMemoryCache.java */

    public Bitmap get(String key) {
        Long loadingDate = loadingDates.get(key);
        if (loadingDate != null && System.currentTimeMillis() - loadingDate > maxAge) {
            //如果超過最大存活時間,則從Cache中移除掉
            cache.remove(key);
            loadingDates.remove(key);
        }

        return cache.get(key);
    }

LruMemoryCache

在LinkedHashMap中儲存了Bitmap的強引用,並限定了強引用的最大記憶體,這裡稍微解釋下LinkedHashMap的構造方法:

/* LruMemoryCache.java */

        this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);

主要是最後一個引數:返回true表示排序的順序是從最遠使用到最近使用,而返回false順序則為插入時的順序。

再來看LruMemoryCache#put()方法的實現:

/* LruMemoryCache.java */

    public final boolean put(String key, Bitmap value) {
        synchronized (this) {
            size += sizeOf(key, value);

            //如果之前沒有沒有新增過,新增後返回null
            Bitmap previous = map.put(key, value);

            //說明該key對應的Bitmap已經存在
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
        }

        //遍歷LinkedList,移除最遠使用的Bitmap,直到小於最大
        //限定的記憶體大小
        trimToSize(maxSize);
        return true;
    }

移除的操作如下:

/* LruMemoryCache.java */

    public final Bitmap remove(String key) {

        synchronized (this) {
            //移除key對應的Bitmap,存在則返回該Bitmap,否則返
            //回null
            Bitmap previous = map.remove(key);
            //如果移除的Bitmap存在
            if (previous != null) {
                size -= sizeOf(key, previous);
            }
            return previous;
        }
    }

我們看到map例項化時並不是執行緒安全的,所以在所有的操作中都有同步鎖。

繼承LimitedMemoryCache

我們知道這類Cache快取都是軟/弱引用和限定記憶體的強引用的結合(強引用是List,軟引用是Map),重寫LimitedMemoryCache主要是來實現removeNext()方法,以指定超過記憶體最大限定後移除Bitmap的規則。

FIFOLimitedMemoryCache

先進先出佇列

/* FIFOLimitedMemoryCache.java */

    public boolean put(String key, Bitmap value) {

        //成功新增到強引用和軟/弱引用中,其中在超出記憶體限定後會
        //對記憶體中的Bitmap縮減
        if (super.put(key, value)) {
            //新增到佇列中
            queue.add(value);
            return true;
        } else {
            return false;
        }
    }

我們看到queue是沒限定記憶體的,難道不怕在我們滑動列表的過程中記憶體溢位?

如果你注意到了這個細節,說明你程式碼看的很仔細,我們來分析下這塊的邏輯:我們在呼叫super.put(key, value)時,如果發現記憶體超出了限定值,我們會根據removeNext()來依次刪除強引用中的Bitmap,而在FIFOLimitedMemoryCache中,removeNext()實現如下:

/* FIFOLimitedMemoryCache.java */

    protected Bitmap removeNext() {
        return queue.remove(0);
    }

LinkedList預設就是根據插入時的順序的,所以直接返回第一個元素,同時從queue中移除了第一個元素,所以也達到了限定記憶體的目的。

/* FIFOLimitedMemoryCache.java */

    public Bitmap remove(String key) {
        Bitmap value = super.get(key);
        //如果軟/弱引用存在
        if (value != null) {
            //從佇列移除
            queue.remove(value);
        }

        //從強引用和軟/弱引用移除
        return super.remove(key);
    }

這個queue與父類強引用其實是一樣的,都是LinkedList,queue之所以存在是因為為了程式碼的一致性,父類中並沒有對外部(包括子類)暴露強引用,我們沒辦法對強引用直接操作,所以大家都是快取一份自己的佇列。

LargestLimitedMemoryCache

在put()時儲存了每個Bitmap的size,這個類主要實現了超過記憶體限定後刪除Bitmap的removeNext()方法:找出佔用記憶體最大的圖片返回。

LRULimitedMemoryCache

利用LinkedHashMap的特性實現LRU的特點,重點也是重寫了removeNext()方法:

/* LRULimitedMemoryCache.java */

    //用LinkedHashMap來儲存Bitmap
    private final Map<String, Bitmap> lruCache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(INITIAL_CAPACITY, LOAD_FACTOR, true));

跟LruMemoryCache中的Map實現方式相似:

/* LRULimitedMemoryCache.java */

    protected Bitmap removeNext() {
        Bitmap mostLongUsedValue = null;
        synchronized (lruCache) {
            Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
            if (it.hasNext()) {
                Entry<String, Bitmap> entry = it.next();
                mostLongUsedValue = entry.getValue();
                it.remove();
            }
        }
        return mostLongUsedValue;
    }

由於LinkedHashMap的特點,以上方法每次迭代lruCache時,都是先刪除最遠使用的。

UsingFreqLimitedMemoryCache

用一個HashMap來儲存每個Bitmap的使用次數,當呼叫put()方法時,使用次數置零,當呼叫get()方法時,使用次數加1。removeNext()的實現方式比較簡單:直接找出hashmap中使用次數最少的,然後返回。

到此,對ImageLoader記憶體快取的分析結束。

ImageLoader快取之磁碟篇

看完記憶體快取的分析,再來看看磁碟快取的邏輯。

相關的介面和類的關係如下:

ImageLoader磁碟關係圖

DiskCache

首先是DiskCache介面定義了磁碟的一些基本操作:

/* DiskCache.java */

public interface DiskCache {

    File getDirectory();

    File get(String imageUri);

    boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException;

    boolean save(String imageUri, Bitmap bitmap) throws IOException;

    boolean remove(String imageUri);

    void close();

    void clear();
}

BaseDiskCache

而基類BaseDiskCache則實現了這些基本操作,並定義了一些預設值

/* BaseDiskCache.java */

    //預設的buffer大小為32k
    public static final int DEFAULT_BUFFER_SIZE = 32 * 1024;

    //預設的圖片格式為PNG
    public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;

    //預設的圖片質量為100
    public static final int DEFAULT_COMPRESS_QUALITY = 100;

save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)方法從輸入流讀取資料,然後根據imageUri,將資料讀取到磁碟上。

這個方法最主要的程式碼是:

/* BaseDiskCache.java */

loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);

我們看看copyStream()方法的實現:

/* BaseDiskCache.java */

    //返回true表示成功的讀取了輸入流,返回false則表示listener
    //中斷了輸入流的讀取。
    public static boolean copyStream(InputStream is, OutputStream os, CopyListener listener, int bufferSize)
            throws IOException {
        int current = 0;
        int total = is.available();
        if (total <= 0) {
            total = DEFAULT_IMAGE_TOTAL_SIZE;
        }

        final byte[] bytes = new byte[bufferSize];
        int count;

        //攔截載入
        if (shouldStopLoading(listener, current, total)) return false;

        while ((count = is.read(bytes, 0, bufferSize)) != -1) {
            os.write(bytes, 0, count);
            current += count;

            //攔截載入
            if (shouldStopLoading(listener, current, total)) return false;
        }

        //如果是buffer型別,flush()
        os.flush();
        return true;
    }

我們看看shouldStopLoading()listener中是怎麼攔截的:

/* BaseDiskCache.java */

    private static boolean shouldStopLoading(CopyListener listener, int current, int total) {
        if (listener != null) {
            boolean shouldContinue = listener.onBytesCopied(current, total);
            if (!shouldContinue) {
                if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) {
                    return true; 
                }
            }
        }
        return false;
    }

當設定了listener攔截資料後,每次得到current和total並處理後,通知回來是否還要繼續讀取輸入流,如果不繼續讀取,則會判斷當前載入進度是否超過了75%,超過75%會強制載入完,否則會停止讀取。

也就是說,當我們不想繼續傳輸的時候,我們只需要將listener的onBytesCopied()方法返回false即可。

而save(String imageUri, Bitmap bitmap)方法則是通過Bitmap#compress(CompressFormat format, int quality, OutputStream stream)方法則是將Bitmap直接快取在imageUri所在的磁碟目錄下。

BaseDiskCache中的File都是通過getFile(String imageUri)來取的:
我們看看這個方法的程式碼:

/* BaseDiskCache.java */

    protected File getFile(String imageUri) {
        String fileName = fileNameGenerator.generate(imageUri);
        File dir = cacheDir;
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

我們發現除了快取目錄cacheDir,還可以通過構造方法設定備用的目錄reserveCacheDir,以便在cacheDir不可用時使用。

UnlimitedDiskCache

UnlimitedDiskCache是BaseDiskCache(無抽象方法)的預設實現,沒有增加任何處理。

LimitedAgeDiskCache

而LimitedAgeDiskCache則是在BaseDiskCache的基礎上,用HashMap快取了每個檔案儲存時的時間戳。

/* LimitedAgeDiskCache.java */

    public File get(String imageUri) {
        File file = super.get(imageUri);
        if (file != null && file.exists()) {
            boolean cached;
            Long loadingDate = loadingDates.get(file);
            if (loadingDate == null) {
                cached = false;
                loadingDate = file.lastModified();
            } else {
                cached = true;
            }

            //如果超過了maxFileAge則直接刪除掉
            if (System.currentTimeMillis() -
                loadingDate > maxFileAge) {
                file.delete();
                loadingDates.remove(file);
            } else if (!cached) {
                //如果檔案存在但是loadingDates中找不到時,將
                //此檔案的時間戳加到loadingDates中
                loadingDates.put(file, loadingDate);
            }
        }
        return file;
    }

LruDiskCache

接下來我們主要分析下LruDiskCache,這個類直接實現了DiskCache,並藉助於專門封裝LRU演算法的DiskLruCache類來完成磁碟的實際操作。

DiskLruCache

讓我們先來看看LruDiskCache主要用到的DiskLruCache中的介面:

返回值型別 方法 方法說明
DiskLruCache open() 建立DiskLruCache例項
Snapshot get() 獲取快取實體Entry的快照-讀
Editor edit() 獲取實體Entry的編輯器-寫
boolean remove() 移除LRU中的實體Entry-刪

例項化 - open()

DiskLruCache的構造方法是private型別的,所以只能通過open()方法獲取DiskLruCache的例項,其它的操作都要是這個例項來完成。

引數的含義分別如下:

返回型別 引數 說明
File directory 快取檔案儲存路徑
int appVersion 應用版本號
int valueCount 每個key對應的value的數量
long maxSize 快取的最大size
int maxFileCount 快取的最大數量

而LruDiskCache中獲取DiskLruCache程式碼如下:

/* LruDiskCache.java */

//appVersion和valueCount為1,maxSize和maxFileCount
//外部傳入。
cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize, cacheMaxFileCount);

讀 - get()

根據key查詢Entry的快照,並提供了Snapshot#getFile(int index)來獲取這個檔案和Snapshot#getInputStream(int index)來獲取輸入流,其中引數index即為open()方法引數valueCount的下標。

LruDiskCache中獲取檔案程式碼如下:

/* LruDiskCache.java */

//根據key取到實體的快照snapshot
snapshot = cache.get(getKey(imageUri));

//通過snapshot的getFile()獲取檔案
return snapshot == null ? null : snapshot.getFile(0);

這裡getFile()引數為0,這是因為我們在前面open()方法時定義了valueCount = 1,每個key只有一個value與之對應。

寫 - edit()

根據key查詢Entry的編輯器,Entry寫入相關的操作都是通過Editor來完成的,然後提供了Editor#newInputStream(int index)構建輸入流或Editor#newOutputStream(int index)構建輸出流。

LruDiskCache中寫入磁碟的程式碼如下:

/* LruDiskCache.java */

//根據key去獲取編輯器editor
DiskLruCache.Editor editor = cache.edit(getKey(imageUri));

//通過editor構建輸出流,這裡生成了dirty檔案
OutputStream os = new 
BufferedOutputStream(editor.newOutputStream(0), bufferSize);

boolean copied = false;

//將輸入流imageStream寫入dirty(tmp格式)檔案
copied = IoUtils.copyStream(imageStream, os, listener, bufferSize);

edit()只是獲取到一個Editor的例項,最後別忘了呼叫Editor#commit()提交或呼叫Editor#abort()丟棄此次編輯。而commit()方法通過將dirty檔案renameTo()為clean檔案完成檔案寫入的。

這裡說說Editor#newInputStream(int index)和Editor#newOutputStream(int index)這兩個方法

前者構建的輸入流是read時需要的,實際呼叫的是Entry#getCleanFile(int i)檔案的輸入流

/* DiskLruCache$Editor.java */

        public InputStream newInputStream(int index) throws IOException {
            synchronized (DiskLruCache.this) {
                try {
                    return new FileInputStream(entry.getCleanFile(index));
                } catch (FileNotFoundException e) {
                    return null;
                }
            }
        }

這個clean檔案是在commit()時,從dirty檔案renameTo()來的。

而後者構建的輸出流是write時需要的,實際呼叫的是Entry#getDirtyFile(int i)檔案的輸出流,這個dirty檔案(帶tmp字尾)生成是在呼叫Editor#newOutputStream(int index)時,在LruDiskCache中是在save()時生成:

/* DiskLruCache$Editor.java */

    public OutputStream newOutputStream(int index) throws IOException {
        synchronized (DiskLruCache.this) {

            File dirtyFile = entry.getDirtyFile(index);
                FileOutputStream outputStream;
            try {
                outputStream = new FileOutputStream(dirtyFile);
            } catch (FileNotFoundException e) {

            }
            return new FaultHidingOutputStream(outputStream);
            }

刪 - remove()

移除LRU快取中的key對應的實體。這個方法一般不需要手動呼叫,因為會在呼叫DiskLruCache#close()、DiskLruCache#flush()或執行cleanupCallable任務(DiskLruCache#remove(String key)、DiskLruCache#get(String key)、DiskLruCache#commit()、DiskLruCache#abort()都有呼叫到)時都會自動呼叫trimToSize()和trimToFileCount()分別根據設定的最大size和最大數量來遍歷LRU佇列lruEntries前面的實體Entry。

對快取部分的思考

看完了快取部分的程式碼,我們可能還會有一些疑惑:

首先,磁碟快取時檔名的生成是根據Uri還是[Uri][width][height],我們看到在ImageLoader中使用displayImage()載入圖片時,直接判斷的是MemoryCache,並沒有判斷DiskCache,而實際使用到DiskCache是在我們取不到MemoryCache時,就會去載入LoadAndDisplayImageTask任務,而這個任務構造方法傳入的引數是Uri,所以磁碟載入是根據Uri生成的檔名。

還有,網路上下載的圖片檔名可能會有一些非法字元,儲存在磁碟的時候可能會異常,這個問題是怎麼解決的呢?

/* BaseDiskCache.java */

    protected File getFile(String imageUri) {
        //根據fileNameGenerator規則生成imageUri檔名
        String fileName = fileNameGenerator.generate(imageUri);
        File dir = cacheDir;
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            if (reserveCacheDir != null && (reserveCacheDir.exists() || reserveCacheDir.mkdirs())) {
                dir = reserveCacheDir;
            }
        }
        return new File(dir, fileName);
    }

通過取imageUri的hashcode或md5就能很好的解決這個問題。

最後一個問題,在分析框架的時候我們說通過配置ImageLoaderConfiguration$Builder的denyCacheImageMultipleSizesInMemory()就能來保證我們不同尺寸的圖片只會有一份資料,而預設的不同尺寸的會有多份資料,這個是怎麼做到的呢?

分析框架時我們就知道,記憶體中存放的key的格式為[imageUri]_[width]x[height],這裡width和height是ImageAware的值,預設情況下,會產生多個尺寸的key。

但當設定了denyCache*()這個方法後,在build()的時候,MemoryCache預設會建立FuzzyKeyMemoryCache例項進行包裝:

/* ImageLoaderConfiguration$Builder.java */

    if (denyCacheImageMultipleSizesInMemory) {
        memoryCache = new FuzzyKeyMemoryCache(memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
    }

第二個引數自定義的Comparator實際就是隻對比key的Uri部分,如果兩者Uri相同,則認為是同一個key。

這樣,在我們put()的時候,如果發現Uri相同的,則刪除之前的,在將新的key對應的Bitmap放入,就達到了一個Uri只對應一份資料的目的。

至此,我們已經分析完了ImageLoader中關於框架和快取的全部。如果對這個專案還有疑問,可以留言討論。

相關推薦

深入解析開源專案Universal-Image-Loader快取

Universal-Image-Loader 是一個優秀的圖片載入開源專案,Github地址在 (Github地址) ,很多童鞋都在自己的專案中用到了。優秀的專案從來都是把簡單留給開發者,把複雜封裝在框架內部。ImageLoader作為Github上S

Universal-Image-Loaderandroid圖片快取

詳細資料整理請加群284568173自行下載pdf 專案介紹: Android上最讓人頭疼的莫過於從網路獲取圖片、顯示、回收,任何一個環節有問題都可能直接OOM,這個專案或許能幫到你。Universal Image Loader for Android的目的是為了實現

開源專案Running Life 原始碼分析

小小回顧 上一篇分析了專案的大體框架和跑步模組一些關鍵技術的實現。今天這篇是Running Life原始碼分析的第二篇,主要探討以下問題: 1、HealthKit框架的使用; 2、貝塞爾曲線+幀動畫實現優雅的資料展示介面; 3、實現一個view的複用機制解決記憶體暴漲的問題;

開源項目Universal Image Loader for Android 說明文檔 (1) 簡單介紹

bst 成功 開源 ica ctu structure 使用 說明 由於  When developing applications for Android, one often facesthe problem of displa

3. 深入研究 UCenter API 加密與解密轉載

method href img 破解 cti subst != efault times 1. 深入研究 UCenter API 之 開篇 (轉載) 2. 深入研究 UCenter API 之 通訊原理(轉載) 3. 深入研究 UCenter API 之

《用Python玩轉資料》專案—線性迴歸分析入門波士頓房價預測

接上一部分,此篇將用tensorflow建立神經網路,對波士頓房價資料進行簡單建模預測。 二、使用tensorflow擬合boston房價datasets 1、資料處理依然利用sklearn來分訓練集和測試集。 2、使用一層隱藏層的簡單網路,試下來用當前這組超引數收斂較快,準確率也可以。 3、啟用函式

【機器人學】機器人開源專案KDL原始碼學習:8KDL的精髓

  首先說一下我的心得: 1. 我認為KDL的精髓是Spatial Vector,結合C++等面向物件的語言可以寫出較好的軟體。 2. 直接閱讀KDL程式碼不適合初學者學習機械臂動力學。 3. 要學習機械臂動力學的話應首先閱讀使用3維向量推導公式的文獻,也就是線速度和角速度獨立分析

【機器人學】機器人開源專案KDL原始碼學習:4機械臂逆動力學的牛頓尤拉演算法

  機械臂的逆動力學問題可以認為是:已知機械臂各個連桿的關節的運動(關節位移、關節速度和關節加速度),求產生這個加速度響應所需要的力/力矩。KDL提供了兩個求解逆動力學的求解器,其中一個是牛頓尤拉法,這個方法是最簡單和高效的方法。    牛頓尤拉法演算法可以分為三個步驟: step1:

【機器人學】機器人開源專案KDL原始碼學習:6笛卡爾空間軌跡規劃、圓弧過渡、姿態插值、梯形速度、pathlength

  本文的內容是對另一篇文章(連結)的補充,對Trajectory_example.cpp涉及到的原理作一些簡單的講解,主要內容是:   (1)機器人路徑規劃圓弧過渡的原理;   (2)機器人路徑規劃梯形波的原理;   (3)機器人末端姿態插值的方法(角-軸);   (4)KDL

【機器人學】機器人開源專案KDL原始碼學習:7examples中的CMakeList.txt檔案解讀

通過學習KDL開源專案的程式碼可以學習CMake構建程式的知識,現簡單介紹一下orocos_kinematics_dynamics-master\orocos_kinematics_dynamics-master\orocos_kdl\examples\CMakeList.txt檔案的指令。

【機器人學】機器人開源專案KDL原始碼學習:5KDL如何求解幾何雅克比矩陣

這篇文章試圖說清楚兩件事:1. 幾何雅克比矩陣的本質;2. KDL如何求解機械臂的幾何雅克比矩陣。 一、幾何雅克比矩陣的本質 機械臂的關節空間的速度可以對映到執行器末端在操作空間的速度,這種對映可以通過一個矩陣來描述,就是幾何雅克比矩陣,瞭解雅克比矩陣需要了解這種對映關係的本質,這

【機器人學】機器人開源專案KDL原始碼學習:3機器人操作空間路徑規劃(Path Planning)和軌跡規劃Trajectory Planning示例

很多同學會把路徑規劃(Path Planning)和軌跡規劃(Trajectory Planning)這兩個概念混淆,路徑規劃只是表示了機械臂末端在操作空間中的幾何資訊,比如從工作臺的一端(A點)沿直線移動到另一端(B點)。而軌跡規劃則加上了時間律,比如它要完成的任務是從A點開始到B點結束,中間

iOS超全開源框架、專案和學習資料彙總1UI

上下拉重新整理控制元件**1. ** --僅需一行程式碼就可以為UITableView或者CollectionView加上下拉重新整理或者上拉重新整理功能。可以自定義上下拉重新整理的文字說明。(推薦)**2. ** --下拉重新整理控制元件4500+star,值得信賴**3. ** --一個效果很酷炫的

電商專案搜尋sql書寫重點

<select id="selectByNameAndProductId" resultMap="BaseResultMap" parameterType="map"> select <include refid="Base_Column_List">&l

深入分析Java Web技術內幕讀書筆記淺析DNS域名解析過程

上一篇文章《淺析Web請求過程》講述的是如何發起HTTP請求,對於請求發起過程中很重要的一個步驟——DNS解析過程的描述是一帶而過,本篇文章將跟著DNS解析過程來分析域名是如何解析的。 一、DNS域名解析步驟 下圖是DNS域名解析的一個示例圖,它

Django專案建立---Templates及擴充套件

1.建立步驟 在APP的根目錄下建立名為templates的目錄,然後在該目錄 下建立HTML檔案(檔案上滑鼠右鍵,選擇New–>HTML File),命名為index.html,程式碼如下: <!DOCTYPE html> &l

區塊鏈開源專案Ripple四、共識1

共識的概念最先由ripple提出,解決的數學問題模型是拜占庭將軍問題,這一節先介紹目前存在的共識機制及其優缺點。 1、Pow工作量證明,就是大家熟悉的挖礦,通過與或運算,計算出一個滿足規則的隨機數,即獲得本次記賬權,發出本輪需要記錄的資料,全網其它節點驗證後一起儲存; 優點

深入理解計算機系統--記憶體定址--linux中分段機制的實現方式

linux中的分段機制 前面說了那麼多關於分段機制的實現,其實,Linux以非常有限的方式使用分段。因為,Linux基本不使用分段的機制(注:並不是不使用,使用分段方式還是必須的,會簡化程式的編寫和執行方式),或者說,Linux中的分段機制只是為了相容IA

開源專案SMSS發開指南——protobuf協議設計

本文的第一部分將介紹protobuf使用基礎以及如何利用protobuf設計通訊協議。第二部分會給出smss專案的協議設計規範和原始碼講解。 一.Protobuf使用基礎 什麼是protobuf protobuf是谷歌研發的一種資料序列化和儲存技術。主要可以用來解決網路通訊中異構系統的通訊和資料持久化,與同類

springboot原始碼解析-管中窺豹系列BeanDefine如何載入十三

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.