android,java只是知識點總結

List  ,HashMap  ,set ,HashTable ,eventBus ,LitPal  ,OkHttp   ,Glide ,熱修復,線程安全,線程池,6.0以及5.0權限管理,7.0分屏畫中畫;材料設計語言,藍牙,wifi,ViewDrapHelper,動畫,排序,RandomAccess,Rxjava,註解,設計模式。類設計,項目結構設計。功能設計。泛型數據結構。SqlLiteOpenHelper。內存泄漏。adb命令等等

1、使用aapt進行apk信息的查看

 

aapt  dump dadging   跟上apk路徑

(必須要注意要配置aapt 環境變量)


2、特殊字符在string.xml中需要進行轉義

一般需要注意的就是< 、> 、@,?等等

可以使用<MSGCONTENT><![CDATA["<( ̄︶ ̄)>" ]]></MSGCONTENT>  類似這樣進行包裝;

也可以是使用 \ ,使用

比如顏文字

<!--顏文字 -->

    <string-array name="emoji_array">

        <item>⊙▽⊙</item>

        <item> ( ؕؔʘ̥̥̥̥ ه ؔؕʘ̥̥̥̥ )? </item>

        <item>( •̅_•̅ ) </item>

        <item><MSGCONTENT><![CDATA["<( ̄︶ ̄)>" ]]></MSGCONTENT></item>

        <item>(๑ ̄ ̫  ̄๑) </item>

        <item>눈_눈 </item>

        <item>ᕙ(⇀‸↼‵‵)ᕗ</item>

        <item> ( ・᷄ ᵌ・᷅ ) </item>

        <item>(৹ᵒ̴̶̷᷄﹏ᵒ̴̶̷᷅৹) </item>

        <item>( ˉ ⌓ ˉ ๑) </item>

        <item> o(〃\'▽\'〃)o </item>

        <item>₍₍ (̨̡ ‾᷄ᗣ‾᷅ )̧̢ ₎₎ </item>

        <item>( ¬_¬) </item>

        <item>( ゚皿゚) </item>

        <item>(▭-▭)✧ </item>

        <item>π_π </item>

        <item>(¬ω¬) </item>

        <item>(。•ˇ‸ˇ•。)</item>

        <item>(。・ω・。)ノ♡</item>

        <item>ू(ʚ̴̶̷́ .̠ ʚ̴̶̷̥̀ ू)</item>

        <item>ლ(●ↀωↀ●)ლ</item>

        <item>(ノ ̄д ̄)ノ</item>

        <item>⸂⸂⸜(രᴗര๑)⸝⸃⸃</item>

        <item>-_-||</item>

        <item>ɿ(。・ɜ・)ɾ</item>

        <item>p(´⌒`。q)</item>

        <item>⊙ω⊙</item>

        <item>ꉂ ೭(˵¯̴͒ꇴ¯̴͒˵)౨”</item>

        <item>(ღ˘⌣˘ღ)</item>

        <item>→_→ </item>

        <item>〜( ̄△ ̄〜)</item>

        <item>←_←</item>

        <item>؏؏☝ᖗ乛◡乛ᖘ☝؏؏</item>

        <item>\@_@</item>

        <item>(^3^)</item>

        <item>≥﹏≤</item>

        <item>^ω^</item>

        <item>^_^</item>

        <item>\^O^/</item>

        <item>T_T</item>

        <item>◑▂◐</item>

        <item>-_-#</item>

    </string-array>


3、android EditText 在獲取到焦點的時候就會彈出軟鍵盤,然後是不會執行onClick方法的,因爲第一次點擊如果是沒有獲取到焦點的話首先會獲取焦點,然後下一次點擊纔會執行onCLick,如果需要首先執行onCLick的話,那麼就要在佈局中設置

android:clickable="true"

android:focusableInTouchMode=“false"


然後點擊的時候會執行onClick,然後在裏邊可以設置

mEditText.setFocusable(true);

mEditText.setFocusableInTouchMode(true);

mEditText.requestFocus();

mEditText.requestFocusFromTouch();


然後就可以獲取到焦點,然後在做軟鍵盤的彈出


4、EditText 添加onScrollListener ,只在5.0以上(不包括5.0)有這個方法,必須要保證最小編譯版本在23


5、關於List源碼學習:


List 是一個接口,它繼承了Collection接口,Collection繼承了Iterable接口,


Iterable裏邊定義了Iterator,foreach,spliterator這麼三個方法

Collection繼承了以後在裏邊添加了add ,remove,clear,hashCode,equals,spliterator,retainAll,

addAll,removeAll,toArray,containsAll,contains等方法

List在繼承了以後添加了get,set,indexOf,lastIndexOf,listIterator,subList,replaceAll,sort這幾個方法。


我們在使用List的時候都是使用它的實現類ArrayList,LinkedList,Vector。


ArrayList的實現:

在實現了List接口以後我們會重寫裏邊所有的方法,然後實現對應的功能,

ArrayList還繼承了AbstracList這個抽象類,這個類繼承自AbstractCollection,然後實現了List接口(抽象類的主要作用就是實現共有部分功能)


但是大部分功能都是在ArrayList中進行的實現。

ArrayList提供了三個構造方法

   1、默認的無參構造方法,默認長度爲10

    public ArrayList() {

        super();

        this.elementData = EMPTY_ELEMENTDATA;

    }


2、指定集合長度

 public ArrayList(int initialCapacity) {

        super();

        if (initialCapacity < 0)

            throw new IllegalArgumentException("Illegal Capacity: "+

                                               initialCapacity);

        this.elementData = new Object[initialCapacity];

    }

3、以集合爲參數 

    public ArrayList(Collection<? extends E> c) {

        elementData = c.toArray();

        size = elementData.length;

        // c.toArray might (incorrectly) not return Object[] (see 6260652)

        if (elementData.getClass() != Object[].class)

            elementData = Arrays.copyOf(elementData, size, Object[].class);

    }


其中的elementData是一個object類型的數組,用來存放集合中的元素。

集合默認長度爲10,如果要添加的元素超過了這個長度就會進行擴容,

調用ensureCapacity,ensureExplicitCapacity,ensureExplicitCapacity,grow()

private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + (oldCapacity >> 1);

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

    }


最後是通過Arrays的copyOf方法複製一個新的長度爲newCapacity的數組,新的長度就等於老的長度除以2 + 老的長度,然後在進行下邊的判斷計算最終的長度。


所以從這些流程可以看到ArrayList在數據頻繁添加的過程中會有頻繁的擴容操作,因爲會有數組的copy過程,如果數據量很大的時候會影響性能,最後是能確定他的最終大小,在初始化的時候直接申請。

而且ArrayList是基於數組來進行數據元素的存儲,所以在插入的過程中都會有元素的移動,插入速度相當慢。

查詢



6 、adb monkey測試  add monkey -p “包名”  -v “次數”


7、關於進入手機設置界面

可以通過抓包來獲取當前ActivityManager來獲取,當前跳轉的activity的信息。下邊是adb 命令

adb logcat | grep ActivityManager


 進入系統設置的所有界面都可以通過Intent


只要設置不同的Settings就可以

Intent appIntent = new Intent();

appIntent  = new Intent(Settings.ACTION_SETTINGS);

context.startActivity(appIntent);


只要替換Settings的值就可以,

如果要進入其中一個應用的詳情設置了Settings以後的話還需要傳遞包名   appIntent.setData(Uri.parse("package:" + context.getPackageName()));



但是android系統沒有專門的權限管理。

如果要進入權限管理必須要對不同系統進行區別,然後根據不同的定製系統來進行處理跳轉。

//vivo權限管理界面

 appIntent.setClassName("com.iqoo.secure", "com.iqoo.secure.safeguard.PurviewTabActivity"); 



然後可以通過adb 命令來啓動一個頁面

db shell am start -n com.android.settings/com.letv.leui.settings.LeUIMainSettings




8、關於EventBus源碼學習


EventBus 的學習首先從 EventBus這個類開始。


這個類主要是用來進行EventBus的初始化,註冊反註冊,發送事件或者移除事件,是整個工具所暴露的可以進行操作的一個工作接口。


EventBus在初始化上邊有倆種方式:

(1)、通過getDefault()

(2)、通過new EventBus()

但是實際創建方式都是通過Builder(建造者模式來進行創建)

跟所有的建造者模式一樣,都是通過一個私有的構造方法以EventBusBuilder對象爲參數來進行對象的創建。


然後調用register來進行註冊。在調用register這個方法的時候會傳遞一個要註冊的對象的。

然後會通過反射來獲取這個對象中的所有的方法。然後會通過方法來找到其中訂閱了EventBus的事件的方法。然後通過subscribe這個方法,把這些方法添加到訂閱列表中。

  public void register(Object subscriber) {

        Class<?> subscriberClass = subscriber.getClass();

        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);

        synchronized (this) {

            for (SubscriberMethod subscriberMethod : subscriberMethods) {

                subscribe(subscriber, subscriberMethod);

            }

        }

    }


反註冊的流程與一般的訂閱者都是一樣的,把訂閱的方法都從訂閱隊列進行刪除。



剩下的就是事件的發送:


因爲整個EventBus的架構就是觀察者模式。


所以事件發送其實就是通過EventBus對象對所有訂閱了這個事件的訂閱者進行通知,然後在他訂閱的方法裏邊就可以進行處理。

    public void post(Object event) {

        PostingThreadState postingState = currentPostingThreadState.get();

        List<Object> eventQueue = postingState.eventQueue;

        eventQueue.add(event);


        if (!postingState.isPosting) {

            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();

            postingState.isPosting = true;

            if (postingState.canceled) {

                throw new EventBusException("Internal error. Abort state was not reset");

            }

            try {

                while (!eventQueue.isEmpty()) {

                    postSingleEvent(eventQueue.remove(0), postingState);

                }

            } finally {

                postingState.isPosting = false;

                postingState.isMainThread = false;

            }

        }

    }


然後postSingleEvent ->postSingleEventForEventType->postToSubscription 然後執行真正的事件發送。

  private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {

        switch (subscription.subscriberMethod.threadMode) {

            case POSTING:

                invokeSubscriber(subscription, event);

                break;

            case MAIN:

                if (isMainThread) {

                    invokeSubscriber(subscription, event);

                } else {

                    mainThreadPoster.enqueue(subscription, event);

                }

                break;

            case BACKGROUND:

                if (isMainThread) {

                    backgroundPoster.enqueue(subscription, event);

                } else {

                    invokeSubscriber(subscription, event);

                }

                break;

            case ASYNC:

                asyncPoster.enqueue(subscription, event);

                break;

            default:

                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);

        }

    }

說是發送,其實是通過反射來調用被訂閱的方法。(只要我們能拿到被訂閱的方法所在的類的對象,就可以執行他裏邊的方法)

也就是通過EventBus中的invokeSubscriber方法

    void invokeSubscriber(PendingPost pendingPost) {

        Object event = pendingPost.event;

        Subscription subscription = pendingPost.subscription;

        PendingPost.releasePendingPost(pendingPost);

        if (subscription.active) {

            invokeSubscriber(subscription, event);

        }

    }


    void invokeSubscriber(Subscription subscription, Object event) {

        try {

            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

        } catch (InvocationTargetException e) {

            handleSubscriberException(subscription, event, e.getCause());

        } catch (IllegalAccessException e) {

            throw new IllegalStateException("Unexpected exception", e);

        }

    }


還有些要注意的類,就是HandlerPoster,這個類主要是維護了一個隊列,用來管理要發送的事件。他的內在實現其實是通過Handler來進行的。handler就不需要多說了。

AsyncPoster 異步Poster,通過實現Runnable,通過ExectorService 進行線程管理。

BackgroundPoster後臺Poster,通過實現Runnable進行實現,通過ExectorService 進行線程管理。

它使用的是Executors.newCachedThreadPool創建的緩存線程池。具體什麼事緩存線程池可以看相關的文章。

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();



總結:

EventBus的整體架構是通過 觀察者模式來進行事件的訂閱,然後在訂閱的事件發生了改變以後通知所有的訂閱者。在這個過程中使用到了反射來進行。在訂閱的時候通過反射獲取到當前對象所定於的方法,在有改變的通過反射執行被訂閱的方法,然後就可以處理自己的邏輯。  

在發送事件的時候(post)我們會判斷當前事件是在什麼線程中執行。然後做相應的處理。

對與事件處理主要使用了Handler(初始化的時候默認是主線程Handler),

使用線程池進行事件的管理。

裏邊用到了多線程的併發處理:使用ThreadLocal  ,CopyOrWriteArrayList;synchronized。

ThreadLocal 會爲每一個線程創建一個副本對象,CopyOrWirteArrayList是通過copy一份數據,然後在Write的時候通過創建一個新的對象,然後對新的對象進行write,然後把新對象賦值給舊對象來保證線程安全。讀取的時候通過使用舊的對象來進行,因爲讀取是不需要關注線程安全問題。

synchronized同步鎖,只有持有這個方法的所的對象才能進入這個方法,其他的都要等待。



9、關於RecycleView的使用

使用RecyvleView的方式很簡單,跟使用ListView,GridView類似,只是RecycleView可以實現比這倆個控件更多的功能。只要通過LayoutManager進行設置。


RecycleView沒有onItemClickListener,需要自己實現。

RecycleView 如果有錯位的問題,那麼肯定也是跟ListView一樣因爲複用引起的,其實修改原理都類似。個人覺得只要把複用的View裏邊的參數都還原成最初的狀態(如果view成爲最原始的狀態,那麼只要數據沒有問題,就不會有錯位的事了),然後通過tag來進行判斷,然後在更新View即可。


RecycleView的adapter裏邊強制使用VIewHolder。爲了減少重複創建對象的消耗(主要是會影響性能)。


RecycleView 滾動條的設置使用的是View的通用滾動條設置。

        android:scrollbars=“horizontal"  //設置滾動條的方向

        android:scrollbarAlwaysDrawHorizontalTrack=“true" //設置總是繪製橫向滾動條

        android:scrollbarThumbHorizontal=“@drawable/scrollbar_line”//橫向滾動條

        android:scrollbarTrackHorizontal=“@drawable/scrollbar_line_track”//橫向滾動條的軌道

        android:paddingBottom="20dp"

        android:scrollbarStyle=“outsideInset" //設置顯示的位置

        android:fadeScrollbars=“false" //是否一直顯示


如果要設置不一樣粗細的滾動條與軌道,可以通過使用不同粗細的進度圖片來實現,也可以通過

使用shape來自定義滾動條與軌道,自定義的時候滾動條的高度要比軌道的高,這樣橫向滾動條與軌道就會不一樣粗細。


10、fresco是一個很強大的圖片加載庫,不同的功能都有相對應的庫模塊來處理。


11、android中點擊電源鍵關閉屏幕的話。如果現在屏幕是豎屏沒有任何影響,如果是橫屏,會銷燬activity,然後在啓動這個Activity 。會導致activity數據的丟失。如果只是數據可以通過onSaveInstance進行保存,但是如果正在進行通話或者長連接的功能,會導致功能中斷。

解決方式:1、使屏幕常亮

2、設置activity的 

android:configChanges=“orientation|keyboardHidden|screenSize"


12、jni 編譯  :

 (1)首先項目的sdk地址

創建native方法,然後點擊studio的make project生成class文件

 (2)然後切換到項目package目錄下 (例如 com.xxx.xxx.xx)

        調用 javaH -d  ../jni “包名 + 類名”

  然後會在jni文件夾下生成.h頭文件。

然後創建c或c++文件,進行代碼的編寫,可以直接複製頭文件到c或c++文件中。

提供的頭文件裏邊的參數需要自定義名稱。是一個空的實現。

(3)配置app.gradle文件 

ndk {


            moduleName"jnilib"


            ldLibs "log", "z", "m"


            abiFilters "armeabi", "armeabi-v7a", "x86"


        }


如果不知道studio支持的ndk可編譯版本可以在gradle.properties添加

android.useDeprecatedNdk=true//可使用過時版本


(如果使用studio編譯以後運行出現dlopen failed: cannot locate symbol "__aeabi_memcpy" referenced by “/data/app/com.example.chenpengfei.uninstallreceiver-2/lib/arm/libjnilib.so”,那麼是因爲sdk版本的問題,23及以下,或者更新studio到最新版本)



13、LinkedList源碼學習:

關於LinkedList 類的功能來說,也是一個集合容器類,實現了List接口,但是他內部的數據結構跟ArrayList不一樣,使用的是鏈表。而實現鏈表主要是通過Node<E>這個類。


他繼承了AbstractSequentialList,而這個類其實是繼承了AbstractList。

他與ArrayList不一樣的就是他實現了Deque接口。


因爲它是基於Node實現的列表,那麼需要關注的就是Node這個類

private static class Node<E> {

        E item;

        Node<E> next;

        Node<E> prev;


        Node(Node<E> prev, E element, Node<E> next) {

            this.item = element;

            this.next = next;

            this.prev = prev;

        }

    }


這個類會保存它上一個節點node,跟下一個節點node。


如果是有倆個節點那麼a 節點的next指向b節點 ,pre也指向b,b的pre指向a,next也指向a,這樣就形成了一個閉環。實現了雙向鏈表。


如果是三個的話  a 節點的next 指向b ,pre指向c,b 節點的next 指向c,pre指向a,而c節點的next指向a,pre指向b。這樣也是一個閉環。


所以LinkedList存儲數據的時候特別快。直接把節點的指向改變了就可以了。

但是存儲的時候也分幾種情況:

如果是直接添加的話不管是添加到首部還是尾部都直接修改節點指向,但是如果是指定下標來進行添加的話,會折半查找到下邊的node節點,然後在該節點處修改節點指向。速度會慢點。

但是查詢的時候就需要類似上邊的折半以後進行查找,然後返回該節點。速度就會比ArrayList慢很多。畢竟人家是實現了RendemAsscess接口,而且內部結構是array,查詢沒的比啊。



14、Vector源碼學習

通過學習ArrayList 與LinkedList以後在學習Vector發現沒有太多要注意的地方。

因爲Vector是跟ArrayList基本上完全相同的,只是在某些地方有點區別。

Vector是線程安全的,在很多方法上都加了線程鎖,synchronize,實現同步。內部結構完全就是ArrayList的結構,只是在容量擴展的時候

這是Vecctor的容量擴展

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?

                                         capacityIncrement : oldCapacity);

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

這是ArrayList的容量擴展

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + (oldCapacity >> 1);

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

    }


可以發現在進行容量擴展的時候 newCapacity的值在取值的時候有點區別

ArrayList是直接擴展爲原來容量的3/2,但是Vector因爲有 一個容量擴充的參數capacityIncrement

,如果不爲0,那麼每次擴展是在原來的基礎上增加capacityIncrement的長度,否則就是原來的2倍。


因爲跟ArrayList很相似,學習的過程中發現比較一下這幾個實現了List接口的類更有用:


ArrayList、Vecotr、LinkedList的區別(源碼角度):


區別:

1、ArrayList、Vector使用的是數組的數據結構來進行數據的存儲,實現了RandomAccess接口提供快速隨機訪問。 而LinkedList是通過鏈表這樣的數據接口來存儲數據,他實現了Deque接口,在隨機訪問方面性能很差,因爲需要遍歷這個鏈表中的節點(雖然是折半以後的遍歷,但是一樣很慢)

2、容量擴展對於LinkedList來說是沒有任何關係的,因爲它不需要。但是對於ArrayList於Vector來說這是很重要的點。因爲他們的初始容量都是10,除非你指定了他的容量。但是在把數據進行添加的過程中,他們會檢查容量是否足夠存放數據,如果不夠就要進行容量的擴展。容量的擴展是通過創建一個新的容量的數組,然後指向上一個數組對象,創建的方式是調用一個系統的arrayCopy方法。

數據量很大的話對與性能有很大影響,使用的時候根據需要可以指定初始容量。

這就是他們之間的又一個不同點,插入效率,很明顯LinkedList很快,但是Vector與ArrayList很慢


Android 虛擬現實開發

開發者中心地址:https://vr.google.com/daydream/developers/


按需要下載自己需要的sdk已經開發工具


15、HashMap源碼學習

 HashMap是一個存儲鍵值對的容器類,通過學習他的源碼來熟悉他的實現。

首先說下HashMap 1.8之前,最基本的實現是通過維護一個Entry[]的數組已經單鏈表來實現的數據存儲。


就是當我們在創建一個HashMap以後,通過調用put方法,會進行很多判斷,最後把我們要存放的數據添加到Entry[]數組中。

public V put(K key, V value) {  

    if (key == null)  

        return putForNullKey(value);  

    // 得到key的哈希碼  

    int hash = hash(key);  

    // 通過哈希碼計算出bucketIndex  

    int i = indexFor(hash, table.length);  

    // 取出bucketIndex位置上的元素,並循環單鏈表,判斷key是否已存在  

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  

        Object k;  

        // 哈希碼相同並且對象相同時  

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  

            // 新值替換舊值,並返回舊值  

            V oldValue = e.value;  

            e.value = value;  

            e.recordAccess(this);  

            return oldValue;  

        }  

    }  


    // key不存在時,加入新元素  

    modCount++;  

    addEntry(hash, key, value, i);  

    return null;  

}  

但是在1.8以後,爲了實現快速查找,添加了紅黑樹。裏邊維護的數組變爲了Node[]數組,一個實現Map.Entry接口的類。這應該是1.8改動最大的地方。


 1     public V put(K key, V value) {

 2     // 對key的hashCode()做hash

 3     return putVal(hash(key), key, value, false, true);

 4 }

 5

 6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

 7                boolean evict) {

 8     Node<K,V>[] tab; Node<K,V> p; int n, i;

 9     // 步驟①:tab爲空則創建

10     if ((tab = table) == null || (n = tab.length) == 0)

11         n = (tab = resize()).length;

12     // 步驟②:計算index,並對null做處理 

13     if ((p = tab[i = (n - 1) & hash]) == null) 

14         tab[i] = newNode(hash, key, value, null);

15     else {

16         Node<K,V> e; K k;

17         // 步驟③:節點key存在,直接覆蓋value

18         if (p.hash == hash &&

19             ((k = p.key) == key || (key != null && key.equals(k))))

20             e = p;

21         // 步驟④:判斷該鏈爲紅黑樹

22         else if (p instanceof TreeNode)

23             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

24         // 步驟⑤:該鏈爲鏈表

25         else {

26             for (int binCount = 0; ; ++binCount) {

27                 if ((e = p.next) == null) {

28                     p.next = newNode(hash, key,value,null);

                        //鏈表長度大於8轉換爲紅黑樹進行處理

29                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  

30                         treeifyBin(tab, hash);

31                     break;

32                 }

                    // key已經存在直接覆蓋value

33                 if (e.hash == hash &&

34                     ((k = e.key) == key || (key != null && key.equals(k))))                                            break;

36                 p = e;

37             }

38         }

39        

40         if (e != null) { // existing mapping for key

41             V oldValue = e.value;

42             if (!onlyIfAbsent || oldValue == null)

43                 e.value = value;

44             afterNodeAccess(e);

45             return oldValue;

46         }

47     }

 

48     ++modCount;

49     // 步驟⑥:超過最大容量 就擴容

50     if (++size > threshold)

51         resize();

52     afterNodeInsertion(evict);

53     return null;

54 }

跟所有的容器類一樣,這個類裏邊也定義size, modcout,loadFactor(負載因子),threshold(判斷是否需要擴展的界限 他的值等於初始容量乘以 負載因子),默認的初始容量是 1<< 4 ,


Hash主要是通過散列表來進行存儲,通過計算key的hash值,然後計算要存放的下標,然後放到數組中對應的位置。如果計算出來的hash相同,那麼比較key是否相等,如果相等,那麼久覆蓋舊值,返回舊值,如果不相同,那麼創建Node對象,然後與該位置中的Node鏈表進行連接,使Node鏈表的最新的節點是新創建的Node對象。


HashMap的擴容是擴容爲 原來的2倍。


因爲HashMap是通過散列表來進行存儲,所以會有空間的浪費,也就是用空間換取時間。

查找的時候通過計算key的hash值,然後計算在數組中的下標,可以快速的進行查找。

HashMap中使用鏈表主要是爲了解決碰撞問題,也就是會有相同的Hash值的問題。




16、HashSet源碼:


HashSet用來存放單一數據,不在是鍵值對,而且存儲到其中的數據沒有順序,但是他可以保證數據不會重複(前提是你要重新hashCode 以及equals方法)。

因爲HashSet的底層實現是通過HashMap來進行實現的。通過他的構造方法可以看到:

    public HashSet(int initialCapacity) {

        map = new HashMap<>(initialCapacity);

    }



   public HashSet(int initialCapacity, float loadFactor) {

        map = new HashMap<>(initialCapacity, loadFactor);

    }


 public HashSet(Collection<? extends E> c) {

        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

        addAll(c);

    }



public HashSet() {

        map = new HashMap<>();

    }


如果對HashMap源碼瞭解的話很簡單就可以看懂。


HashSet提供了add方法進行數據的添加,也提供了容器類基本的功能。

比如刪除,是否包含,是否爲空等等。

使用HashSet的時候遍歷數據是通過HashMap的KeySet這個類進行的。

final class KeySet extends AbstractSet<K> {

        public final int size()                 { return size; }

        public final void clear()               { HashMap.this.clear(); }

        public final Iterator<K> iterator()     { return new KeyIterator(); }

        public final boolean contains(Object o) { return containsKey(o); }

        public final boolean remove(Object key) {

            return removeNode(hash(key), key, null, false, true) != null;

        }

        public final Spliterator<K> spliterator() {

            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);

        }

        public final void forEach(Consumer<? super K> action) {

            Node<K,V>[] tab;

            if (action == null)

                throw new NullPointerException();

            if (size > 0 && (tab = table) != null) {

                int mc = modCount;

                for (int i = 0; i < tab.length; ++i) {

                    for (Node<K,V> e = tab[i]; e != null; e = e.next)

                        action.accept(e.key);

                }

                if (modCount != mc)

                    throw new ConcurrentModificationException();

            }

        }

    }



從這個可以看出,我們可以是用ForEach,或者迭代器。


HashSet實現數據不重複就是重寫了HashCode以及Equals方法:

是在他所繼承的AbstractSet這個類中書寫的:


使用HashSet的時候要注意他不是線程安全的,需要重新要存放數據的HashCode 與Equals方法。

HashSet沒有辦法實現快速存取,如果需要如果需要獲取到某一個位置的數據可以先調用 toArray方法轉換爲數組,然後進行查找.返回的數據順序與插入的順序相反。


HashMap的keySet的效率比entrySet效率低,因爲keySet是通過迭代key來進行迭代,entrySet通過迭代Node對象來進行,不需要通過key進行value的查找,所以速度更快。



17、Popwindow源碼學習:


關於popWindow的源碼其中最主要的是show 以及dismiss的原理;

做過懸浮窗開發的人肯定知道,如果我們要創建一個懸浮在當前Activity或者這個android系統上邊的話,需要使用到的一個類肯定是WindowManager,通過WindowManager的addView的方法添加到界面上去,如果只是添加到當前Activity的話



18、Toast源碼學習

也是通過WindowManager來進行實現的。

toast主要是通過TN這個類來進行最終的顯示以及隱藏,如果要自己來控制顯示以及隱藏可以通過反射來獲取Toast中的mTN對象,然後通過這個對象用反射來調用show以及dismiss。

Toast中TN源碼:

private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                // Don't do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    

        int mGravity;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;


        View mView;
        View mNextView;

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }

        /**
         * schedule handleHide into the right thread
         */
        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

        public void handleShow() {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

        private void trySendAccessibilityEvent() {
            AccessibilityManager accessibilityManager =
                    AccessibilityManager.getInstance(mView.getContext());
            if (!accessibilityManager.isEnabled()) {
                return;
            }
            // treat toasts as notifications since they are used to
            // announce a transient piece of information to the user
            AccessibilityEvent event = AccessibilityEvent.obtain(
                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
            event.setClassName(getClass().getName());
            event.setPackageName(mView.getContext().getPackageName());
            mView.dispatchPopulateAccessibilityEvent(event);
            accessibilityManager.sendAccessibilityEvent(event);
        }        

        public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }

                mView = null;
            }
        }
    }




19、反射學習

關於java的反射:首先反射就是在程序運行的過程中可以動態的修改其狀態以及行爲的一種能力。

現在我們使用反射大部分是通過修改某一個類的其中一個對象的引用或者對其值的修改,達到修改對象的行爲。

如果要對類中的方法或者實現進行修改就需要進行字節碼注入操作,不在反射的學習中。

 關於反射主要是通過Class這個類以及

java.lang.reflect

這個包下邊Method,Field等等的來實現的。

20、android切換到桌面

 Intent mHomeIntent;

                            mHomeIntent = new Intent(Intent.ACTION_MAIN, null);

                            mHomeIntent.addCategory(Intent.CATEGORY_HOME);

                            mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);


                            startActivity(mHomeIntent);



21、關於String ,StringBuffer,StringBuilder的比較


String 類是一個final類型的類,它是不能被修改的,也就是說

String s = “aaaaaa”

s = s + “bbbbb”

因爲String 是不能被修改的,所以上邊的在執行的時候是重新創建一個String 對象,然後 賦值給s 。

所以如果在使用的時候是對String 對象來進行操作,是十分的耗時的,但是如果是直接對字符串進行操作,那麼速度很快,都是jvm來進行操作的。


StringBuilder、StringBuffer是可變字符串對象,我們所有對字符串的操作都在這個對象上邊,如果是對字符串對象進行操作的話,速度上要比String快上很多很多。


他們倆個都是繼承了AbstractStringBuilder這個抽象類,在這個類中實現了大部分的功能,比如getValue,setLength,append ,reverse,等等,這個類還實現了CharSequence接口, 所以StringBuffer與StringBuilder都是在AbstractStringBuilder的基礎上又進行了擴展,他們都實現了相同的三個接口java.io.Serializable, Appendable, CharSequence。


而他們倆個的區別就在於線程安全:  


StringBuffer:線程安全 ,是因爲它裏邊很多方法都是上鎖的,通過synchronized來進行修飾的,所以在多線程中操作時是不會引起對象的因多線程操作而發生的數據不一致問題。

StringBuilder:非線程安全,與StringBuffer正好相反,所有的方法都麼有加鎖。但是如果在非多線程操作中,速度比StringBuffer要快。

22、6.0多進程無法安裝總是提示安裝失敗。應用之間交互也需要設置關聯啓動

23、

5.0以後獲取正在運行的程序只能獲取到當前應用。而後臺正在運行的服務以及應用都只能通過獲取服務然後通過service或者process來進行判斷。

因爲6.0以後應用如果設置多進程是無法安裝的。

獲取方式:

 /**
     * 方法描述:判斷某一Service或者應用是否正在運行
     *
     * @param context     上下文
     * @param serviceName Service的全路徑: 包名 + service的類名 (如果是判斷應用是否安裝直接傳遞包名)
     * @return true 表示正在運行,false 表示沒有運行
     */
    public static boolean isServiceRunning(Context context, String serviceName) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> runningServiceInfos = am.getRunningServices(200);
        if (runningServiceInfos.size() <= 0) {
            return false;
        }
        for (ActivityManager.RunningServiceInfo serviceInfo : runningServiceInfos) {
            Log.e("TAG", serviceInfo.service.getClassName());
            if (serviceInfo.process.equals(serviceName)) {
                return true;
            }
        }
        return false;
    }

24、android aidl進程間通信要注意的點;

現在在做一個sdk用來讓訊飛可以與我們公司的應用進行開發。

因爲是要在進程間通信,所以肯定會使用Aidl來進行。

但是要注意的幾點就是:

(1)進程間通信的話最好是不要把提供服務的service設置爲獨立的進程,要保證跟應用在同一個進程(2)還有就是進程間通信必



25、查看當前應用cpu內、存使用情況

adb shell

top -m 15 -s cpu  //查詢當前cpu佔用前十的應用 

dumpsys meminfo //查看手機上所有的內存信息

dumpsys battery//查看電量信息

dumpsys  meminfo “要查看的應用的包名” //查詢某一個應用的包名


top -d 1 |  grep com.guoyi.qinghua  //查詢某一個應用的cpu使用情況

26、關於android 的應用程序cpu佔用率問題

1、圖片加載(圖片加載使用好第三方框架,儘量不要使用Bitmap.create的方法)

   android動畫對於cpu的消耗還是很大的,如果是可以通過自定實現的動畫使用自定義動畫,如過是複雜動畫,能用ObjectAnimator實現就不要用ValueAnimator來進行實。

不要使用動畫來更新自定義的進度條或者是會根據時間間隔變化的View。可以用Timer或者Handler來替換。

可以減小圖片的大小。

2、線程 (線程確保執行完成,或者可以手動控制)

3、佈局加載(佈局儘量減少佈局層數)

4、網絡(在進程網絡訪問的時候要先判斷網狀態)

5、定位(降低定位的時間間隔)

6、直播軟件的編解碼(在使用七牛播放器的時候,設置硬解碼cpu的佔有率比軟解碼低一半,如果cpu佔用太高切換硬解碼是一個好的選擇)


27、android logo 尺寸 

DENSITYSIZELOCATIONRATIOSCREENMARGIN
XXXHDPI192×192drawable-xxxhdpi4640 DPI12 to 16 pixels
XXHDPI144×144drawable-xxhdpi3480 DPI8 to 12 pixels
XHDPI96×96drawable-xhdpi2320 DPI6 to 8 pixels
HDPI72×72drawable-hdpi1.5240 DPI4 to 6 pixels
MDPI48×48drawable-mdpi1160 DPI3 to 4 pixels
MDPI48×48drawable (Cupcake)1160 DPI3 to 4 pixels
LDPI36×36drawable-ldpi0.75120 DPI2 to 3 pixels
NA512×512Google PlayNANAAs required

28、ython環境配置以後在使用中出現的問題

python代碼需要在開頭插入

#!/usr/bin/python

用來生名python支持,然後通過import可以導入需要的類。

如果出現 -bash: ./****.py: /usr/bin/python^M: bad interpreter: No such file or directory

那麼應該是不同系統編碼格式引起的:在windows系統中編輯的.sh .py文件可能有不可見字符,所以在linux系統下執行會報以上異常信息。一般是因爲windows行結尾和linux行結尾標識不同造成的。

解決:

1)在windows下轉換: 

利用一些編輯器如UltraEdit或EditPlus等工具先將腳本編碼轉換,再放到Linux中執行。轉換方式如下(UltraEdit):File-->Conversions-->DOS->UNIX即可。 

2)linux下直接替換:

sed -i 's/^M//g'  filename (注意^M 在linux 下寫法 按^M 是回車換行符,輸入方法是按住CTRL+v,鬆開v,按m)

3)也可在Linux中轉換: 

首先要確保文件有可執行權限 

#sh>chmod a+x filename 


然後修改文件格式 

#sh>vi filename 


利用如下命令查看文件格式 

:set ff 或 :set fileformat 


可以看到如下信息 

fileformat=dos 或 fileformat=unix 


利用如下命令修改文件格式 

:set ff=unix 或 :set fileformat=unix 


:wq (存盤退出) 或者使用:wq! 進行保存


最後再執行文件 

#sh>./filename   

如果配置了全局的python環境變量的話可以直接使用xxxx.py來執行py文件,或者要通過./xxxx.py


29、Bitmap.createBitmap中參數的理解

x的值不能小於0,width 不能小於0;

x+width 的值不能小於0,也不能大於原bitmap的寬度,

y的值與height與上邊規則相同,

而通過這個方法創建的btimap是通過x與y來控制要截取的bitmap的起始位置,最後的width與height用來確定從x與y的位置截取開始到什麼時候結束。


30、如果.9圖片不正規可能導致android studio編譯出現多線程問題


31、studio中svn的使用

studio中使用svn可以更新指定文件,也可以提交指定文件。

svn指定版本更新會把指定版本以下的代碼都更新下來,不會更新指定版本以上的代碼

svn 中revert 復原代碼,只能復原沒有提交的文件,如果提交時沒有辦法復原


32、RecycleView 的scrollToPosition方法,如果剩下的item個數不夠一屏幕的話無法滾動。

但是可以使用 scrollBy(int x, int y) 滾動屏幕的寬度的距離就可以。

33、內存泄漏的理解,如何防止內存泄漏(以及內存泄漏會引起的問題)

關於內存泄漏其實最難的是定位問題出現。一個良好的代碼習慣,能避免掉很多內存泄漏,但是也不能規避過所有的。以前都是在內存泄漏以後使用工具進行內存分析,找到內存泄漏的地方,然後進行修改,現在發現一個內存泄漏檢測比較簡單的一個工具leakcanary,可以去gitHub上直接查看源碼,然後簡單集成下就可以了。

內存泄漏:內存泄漏就是一個對象被別的對象引用,導致他應該被釋放回收的時候,沒有辦法釋放,對內存的持續佔用,會導致android應用在給別的功能分配內存的時候,可能沒有辦法分配給這個功能那麼多的內存,然後gc開始查找可以釋放的內存,但是因爲內存泄漏了(也就是那麼沒有辦法釋放的對象都是強引用),gc查找以後也不能釋放出足夠這個功能所需要佔用的內存的時候,那麼就會出現內存泄漏。

內存泄漏的解決辦法:可以加大android系統給應用分配的內存上限,但是這個是治標不治本,最主要還是要修改內存泄漏的根本原因。一般發生內存泄漏的地方有(1)匿名內部類的使用(因爲匿名內部類一般都會持有當前對象的引用,那麼在內部類對象沒有釋放的時候,那麼他所持有的引用也不會消失,就會導致內存泄漏)如果可以在Application中添加的回調監聽,一定不要放在activity中,因爲那樣的話,在回調沒有回調的時候,引用也會一直持有;(2)在開發中發現 集合類很容易引起內存泄漏,一般儘量不要把集合類創建爲static類型,否則的話他裏邊的對象都不會得到釋放,除非是從集合中移除。可以參考的文章:http://blog.csdn.net/u012808234/article/details/74942491

34、屏幕適配是android一直以來都很堅挺的問題呢,因爲android的開源性,各個廠商的定製,等等,導致android手機的分辨率是千奇百怪,但是我們適配一般也只適配主流的分辨率,不是所有。

那麼在適配的過程中如何用最小的成本來適配最多屏幕,就是我們開發需要考慮的問題。

android 中推薦使用match_parent,warp_parent, xxxdl ,xxxsp,主要就是爲了適配。


dp(dip或者叫dpi) 代表的是單位英寸內的像素點個數,他是跟像素無關,跟屏幕密度有關的。

android中屏幕密度越大也就是單位英寸內的像素點越多。屏幕顯示的更清晰。

密度的計算方式:1920x1080  5英寸手機(對角線的長度,也就是手機左上角跟右下角的長度)

√(1920^2+1080^2)=2202.9071


2202.9/5=468.7021(ppi)≈469ppi

如果在480px 跟240px上 如果密度相同,那麼1dp所佔的大小也是相等的。這就是跟像素無關

160ppi         1dp  1px   

320ppi         1dp  2px

480ppi         1dp  3px

密度因子以160爲基礎,也就是160 密度因子是1,密度因子= 密度/160 

dp轉px    px = dp ✖️密度因子


知道了這個,但是如果只是簡單的設置dp還是不能夠適配大多數屏幕:

適配方式:

1、佈局適配,根據不同分辨率創建不同的佈局

優點是簡單明瞭,只要根據不同的屏幕創建不同的佈局即可,但是會導致的問題就是,佈局文件的增多,如果裏邊對於圖片的使用很多的話,那麼會導致app打包以後的大小變的很大,要適配大部分屏幕,工作量比較大

2、佈局的時候通過比例來進行適配,比如一個按鈕佔屏幕的幾分之幾

優點就是一套佈局可以適配所有的屏幕,但是不是所有的佈局都能用比例來適配

3、使用一套佈局,然後創建不同分辨率的dimens.xml (創建不同分辨率的values比如values_480x1280)

4、一些動畫或者效果,客戶端如果可以實現的話,而且不是特別難的話,最好是自定義,動態的適配分辨率

5、在代碼中動態修改控件的寬度等等參數


發佈了130 篇原創文章 · 獲贊 53 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章