Sparky


  • 首页

  • 归档

Android如何阅读开源项目源代码

发表于 2017-10-25

Android如何阅读开源项目源代码

开源项目一般分为完整项目和第三方类库,对于完整项目主要分析项目架构,引用第三方类库,总体代码风格等,分析第三方类库主要集中在功能和实现思路,设计思想等。

// 了解功能,考虑实现方式

1 总 —- 功能了解

了解项目功能(Sample 和文档)以及主要分为几个大块。另外明白你的需求,比如 PullToRefresh 的下拉实现。
PS:
(1)大家可以自己先想想如果是自己会怎么去实现这个项目,或许看源码过程中会发现思想碰撞。
(2)如果时间比较紧,可以先 Google 一些该项目相关的文档结合代码看看,帮助快速掌握,不过网上分析文章参差不齐,需谨慎。

// 分析项目代码目录结构 –> 核心类 –> 核心方法 –> 找到分析重心

2 分 —- 详细设计

这里正式开始代码分析,分析过程中如果脑子记不住,多动手记下主要类、函数等作用
(1) 入口对完整 App 来说就是 Manifest 找到入口 Activity,对于工具库从调用接口中判断入口类。然后在 IDE 中一步步深入即可。PS:一般不错的开源项目规范都比较好,类、函数、变量从名字上就可以了解作用,所以如果需要快速掌握原理的话看觉得是重点的函数即可。
(2) 核心类在上面的一步步深入过程中已经接触了不少类,大致了解各个类的主要作用

// 分析重心,分析流程

3 总 —- 总体设计

(1) 功能流程图上面 2-(1) 的过程完成后已经大致了解项目的流程,不费事的话可以简单画下流程及相关类、函数。如 Retrofit、Volley 的请求处理流程,Android-Universal-Image-Loader 的图片处理流程图。
(2) 总体设计整个库分为哪些模块及模块之间的调用关系。如大多数图片缓存会分为 Loader 和 Processer 等模块。

// 顺序分析

4 回顾

这时候从 3-2-1(总体设计->流程图->详细设计->功能介绍)
反序看,大致就能了解作者最初是怎么设计的了对于快速分析可以走 1-2(1)-2(2)-3(1) 的步骤。

View事件传递

发表于 2017-10-25

View事件传递机制

基础概念

  1. Android中的touch事件都被封装到TouchEvent对象中,包括触摸的时间,位置,动作等
  2. Touch事件的类型有ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL等。每个事件都是以ACTION_DOWN开始,以ACTION_UP结束。
  3. 对事件的处理有三种,传递,拦截,消费,分别是dispatchTouchEvent(), onInterceptTouchEvent(),onTouchEvent()和onTouchListener

传递流程

  1. 事件都是从activity.dispatchTouchEvent()开始的,如果没有被拦截就会一直走view的diapathTouchEvent(),从最上层的ViewGroup一直向下传递到最下层的子View,最后子View可以通过onTouch()消费掉事件。如果子View没有消费掉事件,那么事件就会向上传递,这时ViewGroup可以消费事件,如果也没有消费的话,最后会走activity.onTouchEvent();
  2. 事件可以被拦截。ViewGroup可以通过onInterceptTouchEvent()阻止其向下传递。
  3. 如果view没有对ACTION_DOWN进行消费,则其他事件不会传递过来。
  4. onTouchListener优先与onTouchEvent进行消费.

View不处理事件流程

View处理事件流程

Java 多线程

发表于 2017-10-25

Java 多线程

线程状态

Eclipse项目导入Android Studio注意事项

发表于 2017-10-25

Eclipse项目导入Android Studio注意事项:

  1. 如果以前把项目从eclipse导入Android Studio失败过,最好要先删除掉gradle文件(包括build.gradle, gradle文件夹,以及gradlew, gradlew.bat文件)
  2. 如果项目中使用了butterknife,需要删除某些文件,不然会报错:error: duplicate class: class_name$$ViewInjector$$.处理步骤如下: 1:删除eclipse项目中的.apt_generated文件夹和.factorypath文件 2:删除.classpath文件中的节点 3:重新导入到android studio中
  3. android studio右下角的Gradle Console很重要,如果有gradle编译报错什么的,分析里边的日志基本上都可以得到答案.比如有的文件非法,你扔了一个.key文件到drawable文件夹中,在新版Android Studio中是会报错的,但是Messenges中的log可能是很片面的说检测到文件非法,但是在gradle console中可以看到具体哪一个文件出错,去修改.
  4. 如果有.9图片,最好在这个项目级别的build.gradle文件的android{}中添加节点, 因为有很多时候在eclipse上能用的.9图片在as上不能用,如果不能用,是可以在gradle console中显示log的.Error:Execution failed for task ‘:app:mergeDebugResources’. > Some file crunching failed, see logs for details build gradle issues
    aaptOptions {
        cruncherEnabled false
        useNewCruncher false
    }
    
  5. 如果用到google-play-service,导入到Android Studio后会自动添加远程依赖compile ‘com.google.android.gms:play-services:+’,但是最新的google-play-sercice使用到了appcompat-v7中的属性,可能与项目中的某些value值冲突,报值非法的错误,建议改为compile ‘com.google.android.gms:play-services:7.0.0’
  6. 去掉project.property中的项目依赖,单独去添加module依赖
  7. 文件格式转换,可能运行时报非法字符的错误,只需要在右下角点击UTF-8,选择一个其他格式,弹出窗选择convert,完毕后再次点击XX(编码格式),选择utf-8,弹出窗选择convert,保存,重新编译.

Android面试题及答案

发表于 2017-10-25

Android面试题及答案

1. Android中的ANR

概念:
Android中的ANR全称是application not responding。应用程序无响应。应用程序在指定时间内没有响应用户操作,系统就会抛出ANR.在activity中的响应时间是5秒,在broadcase中的响应时间是10秒,在service中的响应时间是20s。注意:anr是系统抛出的异常,程序是捕捉不到的。

解决方法:

  1. 运行在主线程的任何方法都应尽量做更少的事情。特别是,Activity应该在他的关键生命周期方法里尽量少的去做创建操作。
  2. 应用程序应该避免在广播接受者里做耗时或者计算操作。但不再是子线程里做这些任务,如果要做耗时操作,应该启动一个service。
2. Fragment的生命周期

完整的生命周期依次是:
onAttach() –> onCreate() –> onCreateView() –> onActivityCreated() –> onStart() –> onResume() –> onPaulse() –> onStop() –> onDestroyView() –> onDestroy() –> onDetach()

切换到这个Fragment分别执行:
onAttch() –> onCreate() –> onCreateVIew() –> onActivityCreated() –> onStart() –> onResume()

切换到其他Fragment:
onPause() –> onStop() –> onDestroyView()

从其他Fragment切换回来:
onCreateView() –> onActivityCreated() –> onStart() –> onResume()

3. Service的生命周期

service中的4个手动调用的方法:

startService(); 
stopService();
bindService();
unbindService();

service中5个生命周期方法:

onCreate();
onStartCommand();
onBind();
onUnbind();
onDestroy();

startService(); 会执行生命周期中的onCreate() –> onStartCommand();如果一个服务已经启动,不会再执行onCreate(),只会执行onStartCommand();

stopService(): 会执行onDestroy()方法,如果一个服务已经被绑定,必须先解绑才能销毁。

bindService(): 会执行onCreate() –> onBind()方法。

unBindService(): 会执行onUnbindService() –> onDestroy()方法。

startService方式启动的服务,在调用者退出之后仍然存在,bindService启动的服务,如果调用者退出,服务也退出。

4. Android动画

Android中有3中动画 帧动画,补间动画,属性动画

帧动画

就是一张张图片一次播放.在Android中的定义方式是在xml文件中定义标签。

启动可以定义oneshot属性,true表示执行一次,false表示执行多次

补间动画

补件动画分为4种,scale/translate/alpha/rotate
常用用法

AlphaAnimation alphaAnimation = new AlphaAnimation(1f, 0.5f);
alphaAnimation.setDuration(100);
alphaAnimation.setRepeatCount(11);
alphaAnimation.start();
ImageView imageView = null;
imageView.startAnimation(alphaAnimation);

属性动画
对于对象属性的动画。就是通过不断的对值的操作来实现动画效果。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(targetView, propertyName, valueFrom, valueTo);
objectAnimator.setDuration(1000);
objectAnimator.setInterceptor(interceptor);
objectAnimator.start();


ValueAnimator animator = ValueAnimator.ofFloat(0f, 300f);
animator.addUpdateListener();
animator.addListener();
animator.start();
5. Activity的启动方式

standard: 标准模式。是acitvity默认的启动模式,假如a启动了b,那么b就会运行在a所在的任务栈里,而且每次启动一个新的activity,不管栈里是否存在,都会创建新的实例。而且在非activity类型的context中启动标准模式的activity会报错,因为context没有任务栈,想要启动就必须制定待启动的activity的标志位为Intent.FLAG_ACTIVITY_NEW_TASK,这个时候activity会以singleTask方式启动。

singleTask: 栈内复用模式。
如果任务栈中存在这个activity的实例,就不会重新创建,而是去复用这个实例,并将这个实例上的所有activity移除栈。假如activity_a启动了activity_b,又从activity_b跳转回activity_a,此时会先判断activity_a的taskAffinity属性对应的栈中是否存在activity_a,如果没有设置taskAffinity对应的任务栈,就默认是启动他的栈。如果存在taskAffinity对应的任务栈并且,activity指定的栈中存在activity_a,就不会重新创建,而是复用这个activity_a,并将上边所有的activity销毁,如果不存在这个activity_a,就会创建activity_a,如果不存在taskAffinity对应的任务栈,就会创建任务栈,并且创建实例

singleTop: 栈顶复用模式。
如果这个Activity处于栈顶,那么将复用这个栈顶的activity,不会产生新的activity实例。例如activity_a启动activity_b,就会去先判断activity_a所在的栈的栈顶元素是否存在activity_b的实例,如果是,则不会重新去创建activity_b,而是直接引用栈顶实例,如果不是,则要去创建。

singleInstantce: 单实例模式。
只有一个实例,并且单独位于一个任务栈中,这个任务栈中只有一个activity。一旦这个activity被激活,就会复用这个实例。

6. Java引用方式

强引用: 强引用是在程序代码中最普遍存在的,使用方式为Object obj = new Object();或者String str = “strong reference”;
即使程序现在内存不足,也不会被回收,只是系统会抛出OOM异常

软引用:
用来描述一些有用但是不是必需的对象.只有在内存不足的时候JVM才会去回收该对象.

弱引用:
也是用来描述非必需的对象.但是只要垃圾回收器扫描到他,他就会被回收.

虚引用:
在任何时候都有可能被垃圾回收.

7. 自定义view的基本流程
  1. 编写attr.xml文件定义view的属性
  2. 编写layout文件
  3. 在布局文件中获取自定义属性
  4. 重写onMesure
  5. 重写onDraw
8. view的绘制流程
9. view的事件传递机制

基础知识

  • 所有的touch事件都被封装称MotionEvent对象,包括触摸的事件位置动作等
  • 事件类型分为ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL,每个动作都是以ACTION_DOWN开始,以ACTION_UP结束
  • 对事件的处理分为3类,包括传递dispatchTouchEvent,拦截onInterceptTouchEvent,消费onTouchEvent/onTouch

传递流程

  • 事件都是从dispatchTouchEvent(),只要没有拦截就会向下传递,子view可以通过onTouchEvent对事件进行消费
  • 父view可以通过onInterceptTouchEvent对事件进行拦截,阻止其向下传递
  • 如果事件从上到下的传递过程中一直没有停止,而且最底层的view没有对事件进行消费,则事件会从下向上传递,父view可以对事件进行消费,如果也没有消费,就会传递到activity的onTouch中进行消费
  • 如果子view没有消费ACTION_DOWN,之后的事件不会传递过来
  • onTouchListener优先于onTouchEvent对事件进行消费
10. Android IPC机制

Android IPC机制是指Android中多进程环境下出现的进程间通信的处理机制.

多进程会出现的问题

  • 静态变量失效.
  • 线程同步机制失效
  • Application可能被创建多次
  • sharepreference的可靠性降低

解决IPC的方法

  • 通过intent传递bundle数据
  • 通过共享文件
  • 使用Messenger
  • 使用AIDL
  • 使用Socket
  • 使用ContentProvider

【源码解析】 Volley的用法及源码解析

发表于 2017-10-25

前言

Volley是谷歌在2013年I/O大会上推出的新的网络请求框架,如今已经被废弃,取而代之使用okhttp进行网络请求。虽然现在使用场景不多了,但是技术总有光辉的一面,而且也是面试所遇到的最常见的源码问题之一,所以此篇就作为认识源码的入门之作。

Volley简介

Volley是谷歌退出的Android异步网络请求和图片加载库,特别适合网络请求量不大但是请求频繁的场景。

Volley的优点:

  • 非常适合进行数据量不大但是通信频发的网络操作(比如加载大量小图片)
  • 对请求进行了处理,包括服务器ResponseCode(2xx,3xx,…)的处理,请求头的处理,支持重试和取消请求,加入缓存机制,支持自定义请求优先级。
  • 在api-9以上使用httpURLconnection,在api-9一下使用httpclient。
  • 提供简便的图片加载工具ImageLoader。

Volley的缺点:

  • 使用的是httpurlconnection和httpclient,现在主流使用okhttp。
  • 只支持http。
  • 加载图片性能一般。
  • 对大文件下载表现糟糕。

Volley的基本用法

请求结果为JSON字符串(String/xml与此类似)

RequestQueue mQueue = Volley.newRequestQueue(this);

String url = null;

JsonObjectRequest jsonRequest = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() {
    @Override
    public void onResponse(JSONObject response) {
        // TODO
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO
    }
});
mQueue.add(jsonRequest);

图片加载

1 使用ImageRequest与jsonrequest类似
2 使用Volley ImageLoader

RequestQueue mQueue = Volley.newRequestQueue(this);
ImageLoader.ImageCache imageCahce = new ImageLoader.ImageCache() {
    @Override
    public Bitmap getBitmap(String url) {
        return null;
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        // TODO
    }
};
ImageLoader imageLoader = new ImageLoader(mQueue, imageCahce);

String url;

ImageView imageView;
imageLoader.get(url, ImageLoader.getImageListener(imageView, R.id.defalt_img, R.id.error_img));

Volley源码解析

分析上边的Volley的基本用法我们可以知道,发送请求,我们会先去创建一个RequestQueue,然后创建一个Request对象,最后把这个Request对象添加到队列里。
具体到代码里,分别是:

创建请求队列:

RequestQueue mQueue = Volley.newRequestQueue(this);

newRequestQueue()方法如下:

/**
 * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
 *
 * @param context A {@link Context} to use for creating the cache dir.
 * @param stack An {@link HttpStack} to use for the network, or null for default.
 * @return A started {@link RequestQueue} instance.
 */
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {

    // DiskBasedCache的参数
    // 创建缓存目录
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    // Stack的参数
    // 设置userAgent,userAgent是创建AndroidHttpClient的参数
    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    // NetWork对象的参数
    // 判断api等级
    // 如果sdk >= 9,使用httpurlconnection进行网络请求
    // 如果sdk < 9,使用httpclient进行网络请求
    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }

    // RequestQueue的参数
    Network network = new BasicNetwork(stack);

    // 创建RequestQueue对象
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

    queue.start();

    return queue;
}

ReuestQueue方法:

/**
 * Creates the worker pool. Processing will not begin until {@link #start()} is called.
 *
 * @param cache A Cache to use for persisting responses to disk
 * @param network A Network interface for performing HTTP requests
 * @param threadPoolSize Number of network dispatcher threads to create
 * @param delivery A ResponseDelivery interface for posting responses and errors
 */
public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

start()方法:

/**
 * Starts the dispatchers in this queue.
 */
public void start() {
    // 停止现有的网络请求
    // 在stop()方法中,中断mCacheDispatcher线程和NetworkDispatcher线程
    // 下边会分析到这两个线程,缓存线程和网络线程
    stop();  // Make sure any currently running dispatchers are stopped.
    // Create the cache dispatcher and start it.
    // 创建并启动一个缓存线程
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // Create network dispatchers (and corresponding threads) up to the pool size.
    // 创建并启动网络线程,默认是4条
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

stop()方法源码:

/**
 * Stops the cache and network dispatchers.
 */
public void stop() {
    // 中断网络线程和缓存线程
    if (mCacheDispatcher != null) {
        mCacheDispatcher.quit();
    }
    for (final NetworkDispatcher mDispatcher : mDispatchers) {
        if (mDispatcher != null) {
            mDispatcher.quit();
        }
    }
}

在newRequestQueue()方法里只做了两件事:创建了RequestQueue对象,执行了RequestQueue的start方法。

  • 其中创建RequestQueue对象的两个参数,会被设置为RequestQueue对象的字段,分别对应缓存和网络请求的配置。
  • start方法中,先停止网络操作,然后分别创建并启动缓存线程和网络线程。

创建一个请求:

JsonObjectRequest jsonRequest = new JsonObjectRequest();

JsonObjectRequest是Request的子子类,包括两个构造方法和一个复写方法parseNetworkResponse(),网络请求时会根据这个方法的返回值判断是否需要使用缓存数据。

将请求添加到队列:

mQueue.add(jsonRequest);

add()方法的源码:

/**
 * Adds a Request to the dispatch queue.
 * @param request The request to service
 * @return The passed-in request
 */
public <T> Request<T> add(Request<T> request) {
    // Tag the request as belonging to this queue and add it to the set of current requests.
    // 给request设置requestqueue
    request.setRequestQueue(this);

    // 添加request到currentrquests集合中
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }

    // Process requests in the order they are added.
    // 设置request
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    // If the request is uncacheable, skip the cache queue and go straight to the network.
    // 如果设置过request不能缓存,直接添加到网络队列返回
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }

    // Insert request into stage if there's already a request with the same cache key in flight.
    // 默认是可以缓存的
    synchronized (mWaitingRequests) {
        String cacheKey = request.getCacheKey();
        if (mWaitingRequests.containsKey(cacheKey)) {
            // There is already a request in flight. Queue up.
            // 如果已经在缓存队列中,加入到相同的等待队列中
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new LinkedList<>();
            }
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);
            if (VolleyLog.DEBUG) {
                VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
            }
        } else {
            // Insert 'null' queue for this cacheKey, indicating there is now a request in
            // flight.
            // 添加到等待队列
            mWaitingRequests.put(cacheKey, null);

            // 添加到缓存队列
            mCacheQueue.add(request);
        }
        return request;
    }
}

add()方法中,先给request设置队列信息等,然后判断是否允许缓存,如果不允许缓存直接把请求加入网络队列,如果允许或者默认则会判断是否在等待队列中,如果不在等待队列中则添加到等待队列并添加到缓存队列,如果已经在等待对立中,就更新等待队列中该请求的队列信息。

添加到请求队列的时候,最重要的两步就是添加到缓存队列或者添加到网络队列。在创建RequestQueue的时候,会创建一个CacheDispatcher和4个NetworkDispatcher,他们都继承Thread。其中在CacheDispatcher中还持有NetworkDispatcher对象。
下边分别分析这两个线程。

CacheDispatcher

构造方法 // 正常的构造方法

成员变量

boolean DEBUG // Log的打印级别
BlockingQueue<Request<?>> mCacheQueue // 缓存请求队列
BlockingQueue<Request<?>> mNetworkQueue // 网络请求队列
Cache mCache // 缓存信息,请求结果的缓存
ResponseDelivery mDelivery // 请求结果的传递类
boolean mQuit // 结束请求的标志

成员方法

quit() // 结束线程

run () // 业务逻辑

业务流程图
CacheDispatcher流程

NetworkDispatcher

构造方法 // 正常的构造方法

成员变量

BlockingQueue<Request<?>> mQueue // 网络请求队列
Network mNetwork // 网络类,代表了一个可以执行请求的网络
Cache mCache // 缓存结果数据
ResponseDelivery mDelivery // 请求结果传递
boolean mQuit // 取消请求的状态标志

成员方法

quit()  // 结束网络请求
addTrafficStatasTag(Request<E> request) // 给请求设置tag
 run() // 业务逻辑

业务流程图

NetworkDispatcher流程图

我们总结一下流程和代码逻辑:

1 创建RequestQueue

首先会创建一个RequestQueue对象,在这个过程中会创建一个缓存目录和一个httpstack,这个httpstack是根据api等级选择httpURLconnection或者httpclient,然后会执行这个RequestQueue对象的start方法,在这个start方法中会首先取消当前请求,之后创建两个线程缓存线程和网络线程。

2 创建request

创建Request对象

3 讲request添加到RequestQueue

这这个过程中会首先设置request队列标志,然后判断是否需要缓存,如果已经设置过不需要缓存,那就直接添加这个request到网络队列并返回,如果要缓存,会去判断是否已经缓存过,如果没有缓存过,就就添加这个Request到缓存队列和缓存集合中,如果缓存过,需要重新设置这个Request的属性,更新到缓存集合中。

然后在缓存线程和网络线程都存在一个死循环来处理缓存队列和网络队列。

在缓存线程的run方法中,首先获取这个Request对象,如果请求已经被取消,需要结束这个请求,继续下一次请求,如果这个请求没有被缓存过,需要添加这个请求到网络请求队列,继续洗一次请求,已经被缓存过,需要判断请求的结果是否过期,如果过期了还是需要添加到网络队列,继续下一次请求,如果没有过期,会从缓存中获取结果,传递结果给回调处理。

在网络线程的run方法中,首先会获取Request对象,如果请求已经被取消,需要结束这个请求,继续下一次请求,如果没有被取消,就让network对象执行performRequest方法,并得到networkresponse,如果结果没有被修改,并且已经有响应传递,请求结束,继续下一次请求,如果有就需要执行parseNetworkResonse方法,最后判断是否需要缓存,如果需要缓存,就讲缓存结果缓存到mCache中。

后记

请求的生命周期

参考

Volley源码解析

Android NDK开发流程

发表于 2017-10-25

Android NDK开发流程

使用ndk-build方式

  • 配置环境变量
  • 编写native代码
  • 生成与native对应的头文件
  • 利用头文件编写对应的C/C++代码
  • 生成so库
  • 使用so库

使用cmake方式

  • 配置环境变量 //下载ndk,cmake,设置NDK_HOME
  • 创建支持c/c++的项目 // 创建项目时选择c++support
  • 创建c/c++文件,编写C/C++代码
  • 更改cmakelists.txt文件 // add_library/target_link_libraries改为自己的ndk name
  • 在java代码中调用

参考:

cmake

ndk-build

给library module添加远程依赖

发表于 2017-10-25

给library module添加远程依赖

  1. 创建library module项目

    File –> NEW –> New Module –> Library Module

  2. 修改library module下的build.gradle文件

    1. 在library module项目根目录下创建xx.gradle文件

       // 1.maven-插件
      apply plugin: 'maven'
      
      // 2.maven-信息
      ext {// ext is a gradle closure allowing the declaration of global properties
          PUBLISH_GROUP_ID = 'com.flame'
          PUBLISH_ARTIFACT_ID = 'mySDK'
          PUBLISH_VERSION = android.defaultConfig.versionName
      }
      
      // 3.maven-输出路径
      uploadArchives {
          repositories.mavenDeployer {
              //这里就是最后输出地址,在自己电脑上新建个文件夹,把文件夹路径粘贴在此
              //注意”file://“ + 路径,有三个斜杠,别漏了
              repository(url: "file:///Users/flame/Documents/sourceTree/mysdk")
      
              pom.project {
                  groupId project.PUBLISH_GROUP_ID
                  artifactId project.PUBLISH_ARTIFACT_ID
                  version project.PUBLISH_VERSION
              }
          }
      }
      
      //以下代码会生成jar包源文件,如果是不开源码,请不要输入这段
      //aar包内包含注释
      task androidSourcesJar(type: Jar) {
          classifier = 'sources'
          from android.sourceSets.main.java.sourceFiles
      }
      
      artifacts {
          archives androidSourcesJar
      }
      
    2. 在build.gradle中apply这个gradle配置

      apply from xx.gradle    
      
    3. 在gradle菜单中执行xx.gradle中的编译方法

  1. 创建并上传生成的sdk项目到github

    使用git命令上传到github目录

  2. 在应用项目中调用

    在app module的build.gradle根节点下添加repositories节点

    repositories {
        jcenter()
        maven{url "https://github.com/flameandroid/mysdk/raw/master"}
    }
    

    在dependencies中添加compile

    compile('com.flame:mySDK:1.0')
    

【踩坑记录】 android 7.0 签名之INSTALL_PARSE_FAILED_NO_CERTIFICATES

发表于 2017-10-25

【踩坑记录】 android 7.0 签名之INSTALL_PARSE_FAILED_NO_CERTIFICATES

前言

Android更新快,Android studio更新也快,Aradle更新更快

项目在之前的开发环境上没有一点问题(windows 10 / AS 2.2 / JDK 1.8 / gradle 2.2.0 / 设备HONOR 8(Android 7.0未安装app)),正好换电脑,换了一个开发环境(Ubuntu 16.04 / AS 2.3.1 / JDK 1.8 / gradle 2.3.1 / 设备有7.0也有7.0之前)。

导入项目之后,因为是最新的as版本,有一个更新提示
罪魁祸首

google爸爸推荐更新能不更新吗,果断更新,long long ago之后,更新完了,终于俺的项目可以执行了,编译运行一条龙,木有任何问题,接下来就是给测试妹子打包,仍然是木有问题,打包出来之后,还本着不要打包出错的心态(丢人现眼)的心态安装了一次,perfect,木有问题。然后就等着所有bug都测试通过的喜讯了。然而,第一步就挂了。

现象

妹子说安装不上,心想这一定是你的打开方式不对,果断装起逼来,拿过来adb命令走起,果然啊,在我的手机上就可以,在测试的手机上就不行,通过adb报错发现INSTALL_PARSE_FAILED_NO_CERTIFICATES,这啥啊,不懂没关系,google baidu走起来。(网上大多数都是很久之前的过滤掉不要看,关键词不能少)

还有一个现象,打包的时候没有在意,也是一个关键失误。就是在gradle 2.3.1上打包的时候,最后一步要选择APK signature scheme,当时没在意直接选的v2 full apk,不要问为什么,英语好就是这么吊(脑洞翻译一下嘛v1打包jar,v2打包apk)。事实证明就是google给的提示不够明确啊。

原因

Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。

如这里所述,Android 7.0引入了新的签名方案V2。V2方案是对整个APK进行签名,而不是像V1一样只对JAR那样签名。如果您仅使用V2进行登录,并尝试在7.0之前的目标上安装,则会收到此错误,因为JAR本身未签名,并且7.0 之前的PackageManager无法检测V2 APK签名的存在。

解决方法

所以方案也就有两种

  1. 降低gradle版本,像我们这样多人开发,每个人的studio版本都不一样,gradle版本也有不一样的,这样就会好一些。就是要把项目目录下的build.gradle中的gradle配置改一下。比如’classpath ‘com.android.tools.build:gradle:2.3.1’’改为’classpath ‘com.android.tools.build:gradle:2.2.3’’
  2. 如果是个人的话,用越新的东西越能装逼,当然推荐用最新的gradle了,只是在打包的时候注意要兼容7.0以前和以后,这就需要注意把Signature Scheme V1和Signature Scheme V2都选上。

后记

不要害怕遇到问题,遇到问题就记下来装逼啊。

参考

官方文档

解放方案1

解决方案2

1…910
Sparky

Sparky

我是谁?我在做什么?我为什么要这么做?我想做什么?我要怎么做?

99 日志
© 2019 Sparky
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4