android,java知识点总结

monkey

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中需要进行转义

`一般需要注意的就是< 、> 、@,?等等
可以使用"<( ̄︶ ̄)>" 类似这样进行包装;
也可以是使用 ,使用
比如颜文字`

<!--颜文字 -->
    <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

首先确保android的adb命令是ok的。
然后就是monkey命令的参数说明。
adb shell 进入手机命令行
然后就可以执行 monkey 看到 monkey的参数说明

shell@PD1624:/ $ monkey
usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]
              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]
              [--ignore-crashes] [--ignore-timeouts]
              [--ignore-security-exceptions]//忽略安全检查
              [--monitor-native-crashes] [--ignore-native-crashes] //忽略native 异常
              [--kill-process-after-error]//发生错误时杀掉进程
               [--hprof] //生成hprof文件
              [--pct-touch PERCENT] [--pct-motion PERCENT]//该种类型事件的百分比
              [--pct-trackball PERCENT]//轨迹球的事件百分比
               [--pct-syskeys PERCENT]//系统事件百分比
              [--pct-nav PERCENT] [--pct-majornav PERCENT]
              [--pct-appswitch PERCENT] [--pct-flip PERCENT]
              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]
              [--pct-permission PERCENT]
              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]//黑名单
              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]//白名单
              [--wait-dbg] [--dbg-no-events]
              [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]
              [--port port]
              [-s SEED] //设置随机种子数,如果俩次seed值相同,那么对于同一事件那么执行的结果完全相同
              [-v [-v] ...]
              作用:命令行上的每一个-v都将增加反馈信息的详细级别。

Level0(默认-v),除了启动、测试完成和最终结果外只提供较少的信息。
Level1(-v -v),提供了较为详细的测试信息,如逐个发送到Activity的事件信息。
Level2(-v -v -v),提供了更多的设置信息,如测试中选中或未选中的Activity信息。
              [--throttle MILLISEC] [--randomize-throttle]//事件的延迟时间(这个时间设置了以后不是特别准备,如果需要确定要跑多久的monkey的话需要计算执行一次时间所花费的时间,然后用时间除以一个事件的时长算出要执行的次数)
              [--profile-wait MILLISEC]
              [--device-sleep-time MILLISEC]//设备睡眠事件
              [--randomize-script] //随机脚本
              [--script-log]//脚本log
              [--bugreport]//bug报告
              [--periodic-bugreport]//定期bug报告
              [--permission-target-system]
              COUNT //数量
              >> 使用这个命令输出monkey日志到目标地址的文件中(这个不是monkey命令,是命令统一的输出方式)

从上边可以发现一个monkey命令的完整写法

  1. 简单的命令: monkey -v -p com.android.bluetooth 200
    -v log级别

-p 后边跟包名
200 就是要执行事件的次数
这就是一个简单的monkey测试

  1. monkey -v -p com.guoyi.qinghua --pct-majornav 20 --monitor-native-crashes --ignore-security-exceptions --ignore-crashes --ignore-timeouts --kill-process-after-error -s 220 --throttle 300 500000 >> ../monkey.txt
  2. 因为monkey在测试过程中事件的延迟时间(这个时间设置了以后不是特别准确,如果需要确定要跑多久的monkey的话需要计算执行一次时间所花费的时间,然后用时间除以一个事件的时长算出要执行的次数)
    如果--throttle 1000 ,那么计算次数的方式

count= (时长 单位毫秒)/(一次事件的时间)
monkey -v -v -v -p com.xxxx.xxx --throttle 1000 count

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这个类。

他继承了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 尺寸

DENSITY SIZE LOCATION RATIO SCREEN MARGIN
XXXHDPI 192×192 drawable-xxxhdpi 4 640 DPI 12 to 16 pixels
XXHDPI 144×144 drawable-xxhdpi 3 480 DPI 8 to 12 pixels
XHDPI 96×96 drawable-xhdpi 2 320 DPI 6 to 8 pixels
HDPI 72×72 drawable-hdpi 1.5 240 DPI 4 to 6 pixels
MDPI 48×48 drawable-mdpi 1 160 DPI 3 to 4 pixels
MDPI 48×48 drawable (Cupcake) 1 160 DPI 3 to 4 pixels
LDPI 36×36 drawable-ldpi 0.75 120 DPI 2 to 3 pixels
NA 512×512 Google Play NA NA As 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.****.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、在代码中动态修改控件的宽度等等参数

35、开发工程中编程语言的学习以及开发工具:

android,java知识点总结

android,java知识点总结

36 、多项目依赖同一个module,实现module的统一管理

现在做android的基本上都在用studio进行开发,如果开发一个项目还好,但是如果是多个项目,而且他们还有同样在使用的Module,那么就要维护很多套不同项目的,但是缺相同的Moudle,所以使用多项目依赖同样的Moudule可以很有效的减小代码的维护成本,减小开发时间。

方式:

  1. 首先创建一个项目,该项目包含所有公有的Module
  2. 打开要添加公有Module的项目,通过强制引用添加所需要的Module

    打开项目的setting.gradle
    如下配置:
    include ':app', ':framework'
    include ':cuslibrary' ,’:qhlibrary' //添加项目所要包含的Module
    project(':cuslibrary').projectDir = new File(‘../AllModule/cuslibrary') //导入该路径下的Moudle
    project(':qhlibrary').projectDir = new File(‘../AllModule/qhlibrary’) //导入该路径下的Moudle

  3. 然后在该项目的app文件夹的根目录下边的build.gradle中添加Module的依赖

    compile project(':cuslibrary')

     compile project(‘:qhlibrary')
    
  4. 保证不会有相同的文件或者类库的引用,然后运行ok

37、在使用svn的时候如果add 了一个不进行版本管理的文件会出现 E200009的错误

解决方式:svn st | grep ^? | awk '{print $2}' | xargs svn add
上一篇:Android通过Path实现复杂效果(搜索按钮+时钟的实现 +svg实现)


下一篇:php判断上传文件是不是图片,如果是,返回图片格式