标签: Android系统

Android 特定用户使用时系统关机

特定用户使用时系统关机

 

wikiwiki6 · 2 小时 45 分钟前 · 472 次点击

定制设备,用户反馈用时自动关机 机器拿回,无论如何无法复现问题,换了好几台新机器还不行(电源键真的没有卡住,电池有电 让用户找了朋友测试,不关机(用户离机器 10 米距离 真的会有这种灵异事件吗? 大佬们怎么解,怎么排查

17 条回复    2021-08-13 14:37:41 +08:00

wikiwiki6
    1

wikiwiki6   2 小时 44 分钟前

难道只能请走近科学了吗
systemcall
    2

systemcall   2 小时 38 分钟前

如果是插电的东西,感觉可能是客户那边的电压的问题
还有可能是温度、湿度
tiancaixiaoshuai
    3

tiancaixiaoshuai   2 小时 24 分钟前

我遇到过相似的问题,你可以参考一下

几年前买了 vivo x510t,手机玩一段时间,再打开*品飞车的时候就会自动关机,有关机画面的那种,重新开机,再打开*品飞车,随便怎么玩,都没事

要说是内存不够,重新开机后打开多个软件,再打开*品飞车也没事,只要是重启过就一切正常,但是用一段时间,就会出问题

你可先用有问题的机器正常使用一天,再打开软件试试

wikiwiki6
    4

wikiwiki6   2 小时 4 分钟前

@systemcall 同样的环境,换个人就可以,太离谱了
wikiwiki6
    5

wikiwiki6   1 小时 57 分钟前

客户那边很生气,说我们推卸责任,但这太离谱了
est
    6

est   1 小时 10 分钟前

电磁脉冲
826540272
    7

826540272   1 小时 9 分钟前

走近科学 请
Hstar
    8

Hstar   1 小时 1 分钟前

派个人跟着客户,看他怎么用的
yfugibr
    9

yfugibr   1 小时 0 分钟前 via Android

和网络连接有关系没
goodryb
    10

goodryb   1 小时 0 分钟前

@wikiwiki6 #5 肯定是有什么因素没抓到,让客户现场重现故障

wikiwiki6
    11

wikiwiki6   29 分钟前

@goodryb 用户拍了视频,甚至手都没碰,还是有问题

wikiwiki6
    12

wikiwiki6   29 分钟前

@est 现实中有这种故障吗?
wikiwiki6
    13

wikiwiki6   28 分钟前

@yfugibr 这种问题应该跟网络没啥关系
yuancoder
    14

yuancoder   18 分钟前

这个就是用户的问题吧
since1997
    15

since1997   14 分钟前

给客户换台新的
no1xsyzy
    16

no1xsyzy   10 分钟前

奉上经典:控制变量法
你需要人员去现场严格控制变量。

其次是关机的情况是否可以暗示导致关机的物理界面,比如是电源按钮被触发,可以带示波器测量电源按钮的芯片引脚。**端的情况下,有可能是现场或者用户携带物产生的某个电磁信号被芯片误认为电源按钮被按下。

est
    17

est   7 分钟前

@wikiwiki6 有。网线、USB3 之间会相互干扰。2.4G 蓝牙之类的干扰也很多。但是对电源的干扰没具体摸过。

开发的一个 App,在华为(HMOS)手机上被华为风险管控中心检测出病毒

开发的一个 App,在华为(HMOS)手机上被华为风险管控中心检测出病毒

大家有遇到过么?如何处理?

安装到华为手机上后,在风险管控中心报出风险应用,提示:

感染病毒:Risk/Android.E_Ads.Rogue
危险等级:中
详情:[流氓广告]应用包含影响功能正常使用或者难以关闭的流氓广告,请谨慎使用。

我们的应用非常正规,只接了几家大厂的广告 SDK,如穿山甲、百度、广点通等,无其它小厂或者不明来路的广告 SDK 。

测试也给出一个奇怪的情况:同一批打出来的渠道包,只有 sc_vivio 渠道安装后会报这个,其它渠道未有发现。

谢谢各位 V 友大神们的响应。

11 条回复    2021-07-30 03:18:26 +08:00

dorothyREN
    1

dorothyREN   3 天前

直接不兼容华为就完事了。
alfchin
    2

alfchin   3 天前 via iPhone   ❤️ 5

一看就是 vivo 渠道打包的有问题
联系 vivo 那边协调看看
luodaoyi
    3

luodaoyi   3 天前   ❤️ 2

vivo 在搞事情
tin3w5
    5

tin3w5   3 天前 via iPhone   ❤️ 2

强势点,支持好 android 和 iOS 就好了。臭毛病不能惯着当你把它养肥了,他才不会记得你的好,不像 Google 、Apple 一样用各种规则打击你这种创作者就不错了。

这也许是你唯一一次机会对强权说不——上学的时候可能被老师强迫、家里可能被父母强迫、接受教育被应试教育强迫、公司里可能还要被老板强迫 996/007 。这来之不易的机会,难道不应该好好珍惜吗?

araraloren
    6

araraloren   3 天前   ❤️ 4

@tin3w5 这边建议你不要生出来,不然就会被强迫
hxk1990
    7

hxk1990   3 天前   ❤️ 2

@dorothyREN 脑子有泡?
ETO
    8

ETO   3 天前

@dorothyREN 嗯?
iiusky
    9

iiusky   3 天前

楼上都不审题吗?都没看到只有某渠道打包出来有这个问题?和华为有毛关系?
jinhan13789991
    10

jinhan13789991   2 天前

不是,你 vivo 的渠道包为啥安装在华为手机上? 这不是矛盾吗?

tomato1111
    11

tomato1111   13 小时 45 分钟前 via Android

你是不是被 vivo 二次打包了。。。用 v2 签名了吗

Android实现滑动的几种方式

View的滑动对于View交互性及效果有很大影响,我们可以通过以下四种方式来实现View的滑动,准确地说是View位置的改变。要改变View的位置,首先我们需要了解Android的坐标系,因为View的是通过坐标来定位的。

*对坐标系

Android系统中,屏幕的*左上角为坐标原点,如下图所示。

%title插图%num

屏幕*左上角的点为坐标原点,向右向下分别为x轴和y轴

视图坐标系

视图坐标系是在View的层级体系中使用到的,View的父布局*左上角为坐标原点,向右向下为x轴和y轴,如下图所示:

%title插图%num

几个容易混淆的方法:

getX():视图坐标系点的X坐标
getRawX():*对坐标系点的X坐标
getLeft():视图坐标系View左边框距离ViewGroup左边框距离
getTranslationX():View的偏移量,初始为0,当View发生平移时,其值会变,向右为正,向左为负。
其中view.getX() = view.getLeft() + view.getTranslationX(),而get*Y同理。

1. 通过改变View的布局位置

View的layout方法用来将View放到布局的合适位置,我们可以通过这个方法改变它的left,top,right,bottom参数的值来改变它在布局中的位置。在此基础上,如果我们在用户手指移动的过程中不断地改变View的位置就可以让View跟随手指移动。要实现View跟随用户手指滑动,我们可以监听用户手指的动作(按下,移动,。。。)计算偏移量,通过layout改变View的位置即可。

如下代码则通过layout实现View跟随手指滑动(重写View的onTouchEvent方法)

private int lastX;
private int lastY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = x – lastX;
int offY = y -lastY;
layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
break;
}
return true;
}

这里通过相对坐标计算偏移量完成View的滑动,还可以通过*对坐标计算偏移量,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
lastRawX = rawX; // 重置坐标
lastRawY = rawY; // 重置坐标
break;
}
return true;
}

与相对坐标不同的是,使用*对坐标需要在move事件结束后重置上一次手指的坐标值,这样才能准确地计算出偏移量。

为什么*对坐标要重置?那是如果不重置的话每次移动都是拿新的坐标与*开始的坐标比较得到偏移量,而*开始的坐标是View的初始位置手指按下的坐标,View每次移动的偏移量应该是新位置的坐标减去上一次的坐标,所以每次移动后需要更新上一次的坐标。

除了使用View的layout方法重新布局View外,还可以使用offsetLeftAndRight和offsetTopAndBottom方法重新布局View,同样可以实现View的滑动效果。代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
offsetLeftAndRight(offX);
offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

还可以通过View的改变布局参数LayoutParams的leftMargin和topMargin属性值改变View的位置,实现View的滑动,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
// offsetLeftAndRight(offX);
// offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
((ViewGroup.MarginLayoutParams)layoutParams).leftMargin += offX;
((ViewGroup.MarginLayoutParams)layoutParams).topMargin += offY;
setLayoutParams(layoutParams);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

改变View的布局参数需要注意的是这个View(或ViewGroup)必须有一个父布局,同时还需要注意布局参数的类型(如LinearLayout.LayoutParams,FrameLayout.LayoutParams),不过可以使用万能的MarginLayoutParams,这样就可以不用考虑布局参数类型了。

2. 使用scrollTo和scrollBy

View类有scrollTo和scollBy方法,它们可以改变View内容的位置,scrollTo表示移动到某个坐标点,scrollBy表示移动多少偏移量。我们可以通过scrollBy实现View跟随手指的滑动,代码如下:

private int lastRawX;
private int lastRawY;

@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG,”onTouchEvent() down”);
lastRawX = rawX;
lastRawY = rawY;
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG,”onTouchEvent() move”);
int offX = rawX – lastRawX;
int offY = rawY -lastRawY;
// offsetLeftAndRight(offX);
// offsetTopAndBottom(offY);
// layout(getLeft()+offX,getTop()+offY,getRight()+offX,getBottom()+offY);
// ViewGroup.LayoutParams layoutParams = getLayoutParams();
// ((ViewGroup.MarginLayoutParams)layoutParams).leftMargin += offX;
// ((ViewGroup.MarginLayoutParams)layoutParams).topMargin += offY;
// setLayoutParams(layoutParams);
((View)getParent()).scrollBy(-offX,-offY);
lastRawX = rawX;
lastRawY = rawY;
break;
}
return true;
}

这里你可能会有所迷惑,为什么调用scrollBy的是View的父View(ViewGroup)?为什么偏移量是负的?

首先scrollBy移动的是View的内容content,而不是View本身,如TextView的content为文本,ImageView的content为drawable,而ViewGroup的content是View或是ViewGroup,所以要移动当前View本身,我们就需要通过它的ViewGroup改变自己的内容从而改变View本身的位置。其次,我们真正操作的是View的父控件ViewGroup,要让View往左(上/右/下)移,应该要让ViewGroup往相反方向移动,也就是右(下/左/上),所以偏移量就是相反的(负的)。下面贴上一张图,感受一下。

%title插图%num

3. 使用Scroller类实现View平滑移动

Android为View的滑动提供了Scroller辅助类,它本身并不能导致View滑动,需要借助computeScroll和ScrollTo方法完成View的滑动。使用Scroller类完成View的平滑,需要通过以下三个步骤:

(1)创建Scroller类

通常在自定义View的构造方法中完成Scroller类的初始化

mScroller = new Scroller(context);
1
(2)重写computeScroll方法

@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller滑动是否执行完毕
if(mScroller.computeScrollOffset()){
((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
// 通过重绘让系统调用onDraw,onDraw中又会调用computeScroll,如此不断循环,直到Scroller执行完毕
invalidate();
}
}

这里需要注意的是computeScroll方法在onDraw中会被调用,因此需要调用invalidate方法通知View调用onDraw重绘,然后再调用computeScroll完成View的滑动,过程为invalidate->onDraw->computeScroll->invalidate->…,无限循环直到mScroller的computeScrollOffset返回false,也就是滑动完成。

(3)调用Scroller类的startScroll方法开启滚动过程

public void smoothScrollBy(int dx,int dy){
mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,dy,2000);
invalidate(); // 必须调用改方法通知View重绘以便computeScroll方法被调用。
}

接下来就开始模拟滑动过程了,重写onTouchEvent方法,代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
int offX = x – lastX;
int offY = y -lastY;
smoothScrollBy(-offX,-offY);
break;
}
return true;
}

计算偏移量的方法和上面一样,这里实现的效果是手指离开时,View会在2秒内平滑到手指离开时的位置。

4. 使用属性动画实现View的滑动

属性动画可以改变View的属性,那么我们可以通过属性动画改变View的x和y属性从而改变View的位置实现View的滑动,代码如下:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void animationScroll(float dx, float dy){
Path path = new Path();
path.moveTo(dx,dy);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, “x”, “y”, path);
objectAnimator.start();
}

通过执行ObjectAnimator改变x和y属性,我们需要新的x和y属性值,可以通过重写onTouchEvent方法得到新的x和y属性值,代码如下:

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
int offX = x – lastX;
int offY = y -lastY;
animationScroll(getX()+offX,getY()+offY);
break;
}
return true;
}
————————————————

Android内存优化之避免内存泄漏

前言

内存泄漏向来都是内存优化的重点,它如同幽灵一般存于我们的应用当中,有时它不会现身,但一旦现身就会让你头疼不已。因此,如何避免、发现和解决内存泄漏就变得尤为重要。这一篇我们先来学习如何避免内存泄漏。

1.什么是内存泄漏

我们知道,每个应用程序都需要内存来完成工作,为了确保Android系统的每个应用都有足够的内存,Android系统需要有效地管理内存分配。当内存不足时,Android运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法,
讲到根搜索算法,如下图所示:
VQx9PK.png
从上图看以看出,Obj4是可达的对象,表示它正被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然互相引用,但是因为他们到GC Roots是不可达的所以它们仍旧会标记为可回收的对象。

内存泄漏就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。此时,如果Obj4是一个没有用的对象,但它仍与GC Roots是可达的,那么Obj4就会内存泄漏。
内存泄漏产生的原因,主要分为三大类:
1.由开发人员自己编码造成的泄漏。
2.第三方框架造成的泄漏。
3.由Android 系统或者第三方ROM造成的泄漏。
其中第二种和第三种有时是不可控的,但是*种是可控的,既然是可控的,我们就要尽量在编码时避免造成内存泄漏,下面就来列举出常见的内存泄漏的场景。

2.内存泄漏的场景

2.1 非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。

JAVA

public class SecondActivity extends AppCompatActivity {
    private static Object inner;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createInnerClass();
                finish();
            }
        });
    }

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();//1
    }
}


当点击Button时,会在注释1处创建了非静态内部类InnerClass的静态实例inner,该实例的生命周期会和应用程序一样长,并且会一直持有SecondActivity 的引用,导致SecondActivity无法被回收。

2.2 匿名内部类的静态实例

和前面的非静态内部类一样,匿名内部类也会持有外部类实例的引用。

JAVA

public class AsyncTaskActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
                finish();
            }
        });
    }
    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {//1
            @Override
            protected Void doInBackground(Void... params) {
                while (true) ;
            }
        }.execute();
    }
}

在注释1处实例化了一个AsyncTask,当AsyncTask的异步任务在后台执行耗时任务期间,AsyncTaskActivity 被销毁了,被AsyncTask持有的AsyncTaskActivity实例不会被垃圾收集器回收,直到异步任务结束。
解决办法就是自定义一个静态的AsyncTask,如下所示。

JAVA

public class AsyncTaskActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
                finish();
            }
        });
    }
    void startAsyncTask() {
        new MyAsyncTask().execute();
    }
    private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            while (true) ;
        }
    }
}

与AsyncTask类似的还有TimerTask,这里就不再举例。

2.3 Handler内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。

JAVA

public class HandlerActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        button = (Button) findViewById(R.id.bt_next);
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(), 60000);
                finish();
            }
        });
    }
}

Handler 是非静态的匿名内部类的实例,它会隐性引用外部类HandlerActivity 。上面的例子就是当我们点击Button时,HandlerActivity 会finish,但是Handler中的消息还没有被处理,因此HandlerActivity 无法被回收。
解决方法就是要使用一个静态的Handler内部类,Handler持有的对象要使用弱引用,并且在Activity的Destroy方法中移除MessageQueue中的消息,如下所示。

JAVA

public class HandlerActivity extends AppCompatActivity {
    private Button button;
    private MyHandler myHandler = new MyHandler(this);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myHandler.sendMessageDelayed(Message.obtain(), 60000);
                finish();
            }
        });
    }
    public void show() {
    
    }
    private static class MyHandler extends Handler {
        private final WeakReference<HandlerActivity> mActivity;

        public MyHandler(HandlerActivity activity) {
            mActivity = new WeakReference<HandlerActivity2>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            if (mActivity != null && mActivity.get() == null) {
                mActivity.get().show();
            }
        }
    }
    @Override
    public void onDestroy() {
        if (myHandler != null) {
            myHandler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }
}

MyHandler是一个静态的内部类,它持有的 HandlerActivity对象使用了弱引用,并且在onDestroy方法中将Callbacks和Messages全部清除掉。
如果觉得麻烦,也可以使用避免内存泄漏的Handler开源库WeakHandler。

2.4 未正确使用Context

对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,比如如下的单例模式:

JAVA

public class AppSettings { 
 private Context mAppContext;
 private static AppSettings mAppSettings = new AppSettings();
 public static AppSettings getInstance() {
  return mAppSettings;
 }
  
 public final void setup(Context context) {
  mAppContext = context;
 }
}

mAppSettings作为静态对象,其生命周期会长于Activity。当进行屏幕旋转时,默认情况下,系统会销毁当前Activity,因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄露。
解决方法就是使用Application的Context:

JAVA

public final void setup(Context context) {
 mAppContext = context.getApplicationContext(); 
}

2.5 静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。

JAVA

public class SecondActivity extends AppCompatActivity {
    private static Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
 }   

2.6 WebView

不同的Android版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在着很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

2.7 资源对象未关闭

资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

2.8 集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

2.9 Bitmap对象

临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。

2.10 监听器未关闭

很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。

Android系统启动流程之解析init进程启动过程

前言
作为“Android框架层”这个大系列中的*个系列,我们首先要了解的是Android系统启动流程,在这个流程中会涉及到很多重要的知识点,这个系列我们就来一一讲解它们,这一篇我们就来学习init进程。

1.init简介
init进程是Android系统中用户空间的*个进程,作为*个进程,它被赋予了很多*其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init。本文将基于Android7.0源码来分析Init进程。

2.引入init进程
说到init进程,首先要提到Android系统启动流程的前几步:
1.启动电源以及系统启动
当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。
2.引导程序Bootloader
引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
3.linux内核启动
内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的*个进程。
4.init进程启动

讲到第四步就发现我们这一节要讲的init进程了。关于Android系统启动流程的所有步骤会在本系列的*后一篇做讲解。

3.init入口函数
init的入口函数为main,代码如下所示。
system/core/init/init.cpp

int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), “ueventd”)) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), “watchdogd”)) {
return watchdogd_main(argc, argv);
}
umask(0);
add_environment(“PATH”, _PATH_DEFPATH);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], “–second-stage”) != 0);
//创建文件并挂载
if (is_first_stage) {
mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount(“proc”, “/proc”, “proc”, 0, “hidepid=2,gid=” MAKE_STR(AID_READPROC));
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);
}
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE(“init %s started!\n”, is_first_stage ? “first stage” : “second stage”);
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open(“/dev/.booting”, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
//初始化属性相关资源
property_init();//1
process_kernel_dt();
process_kernel_cmdline();
export_kernel_boot_props();
}

//启动属性服务
start_property_service();//2
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
Parser& parser = Parser::GetInstance();
parser.AddSectionParser(“service”,std::make_unique<ServiceParser>());
parser.AddSectionParser(“on”, std::make_unique<ActionParser>());
parser.AddSectionParser(“import”, std::make_unique<ImportParser>());
//解析init.rc配置文件
parser.ParseConfig(“/init.rc”);//3

while (true) {
if (!waiting_for_exec) {
am.ExecuteOneCommand();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (am.HasMoreCommands()) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR(“epoll_wait failed: %s\n”, strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}

init的main方法做了很多事情,我们只需要关注主要的几点,在注释1处调用 property_init来对属性进行初始化并在注释2处的 调用start_property_service启动属性服务,关于属性服务,后面会讲到。注释3处 parser.ParseConfig(“/init.rc”)用来解析init.rc。解析init.rc的文件为system/core/init/init_parse.cpp文件,接下来我们查看init.rc里做了什么。

4.init.rc
init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本,它主要包含五种类型语句:
Action、Commands、Services、Options和Import。init.rc的配置代码如下所示。
system/core/rootdir/init.rc

on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom

on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
# set RLIMIT_NICE to allow priorities from 19 to -20
setrlimit 13 40 40

这里只截取了一部分代码,其中#是注释符号。on init和on boot是Action类型语句,它的格式为:

on <trigger> [&& <trigger>]* //设置触发器
<command>
<command> //动作触发之后要执行的命令

为了分析如何创建zygote,我们主要查看Services类型语句,它的格式如下所示:

service <name> <pathname> [ <argument> ]* //<service的名字><执行程序路径><传递参数>
<option> //option是service的修饰词,影响什么时候、如何启动services
<option>

需要注意的是在Android 7.0中对init.rc文件进行了拆分,每个服务一个rc文件。我们要分析的zygote服务的启动脚本则在init.zygoteXX.rc中定义,这里拿64位处理器为例,init.zygote64.rc的代码如下所示。
system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks

其中service用于通知init进程创建名zygote的进程,这个zygote进程执行程序的路径为/system/bin/app_process64,后面的则是要传给app_process64的参数。class main指的是zygote的class name为main,后文会用到它。

5.解析service
接下来我们来解析service,会用到两个函数,一个是ParseSection,它会解析service的rc文件,比如上文讲到的init.zygote64.rc,ParseSection函数主要用来搭建service的架子。另一个是ParseLineSection,用于解析子项。代码如下所示。
system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
std::string* err) {
if (args.size() < 3) {
*err = “services must have a name and a program”;
return false;
}
const std::string& name = args[1];
if (!IsValidName(name)) {
*err = StringPrintf(“invalid service name ‘%s'”, name.c_str());
return false;
}
std::vector<std::string> str_args(args.begin() + 2, args.end());
service_ = std::make_unique<Service>(name, “default”, str_args);//1
return true;
}

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
const std::string& filename, int line,
std::string* err) const {
return service_ ? service_->HandleLine(args, err) : false;
}

注释1处,根据参数,构造出一个service对象,它的classname为”default”。当解析完毕时会调用EndSection:

void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}

接着查看AddService做了什么:

void ServiceManager::AddService(std::unique_ptr<Service> service) {
Service* old_service = FindServiceByName(service->name());
if (old_service) {
ERROR(“ignored duplicate definition of service ‘%s'”,
service->name().c_str());
return;
}
services_.emplace_back(std::move(service));//1
}

注释1处的代码将service对象加入到services链表中。上面的解析过程总体来讲就是根据参数创建出service对象,然后根据选项域的内容填充service对象,*后将service对象加入到vector类型的services链表中。,

6.init启动zygote
讲完了解析service,接下来该讲init是如何启动service,在这里我们主要讲解启动zygote这个service。在zygote的启动脚本中我们得知zygote的class name为main。在init.rc有如下配置代码:
system/core/rootdir/init.rc


on nonencrypted
# A/B update verifier that marks a successful boot.
exec – root — /system/bin/update_verifier nonencrypted
class_start main
class_start late_start

其中class_start是一个COMMAND,对应的函数为do_class_start。我们知道main指的就是zygote,因此class_start main用来启动zygote。do_class_start函数在builtins.cpp中定义,如下所示。

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
ServiceManager::GetInstance().
ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
return 0;
}

来查看StartIfNotDisabled做了什么:
system/core/init/service.cpp

bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;
}

接着查看Start方法,如下所示。

bool Service::Start() {
flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
time_started_ = 0;
if (flags_ & SVC_RUNNING) {//如果Service已经运行,则不启动
return false;
}
bool needs_console = (flags_ & SVC_CONSOLE);
if (needs_console && !have_console) {
ERROR(“service ‘%s’ requires console\n”, name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}
//判断需要启动的Service的对应的执行文件是否存在,不存在则不启动该Service
struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
ERROR(“cannot find ‘%s’ (%s), disabling ‘%s’\n”,
args_[0].c_str(), strerror(errno), name_.c_str());
flags_ |= SVC_DISABLED;
return false;
}


pid_t pid = fork();//1.fork函数创建子进程
if (pid == 0) {//运行在子进程中
umask(077);
for (const auto& ei : envvars_) {
add_environment(ei.name.c_str(), ei.value.c_str());
}
for (const auto& si : sockets_) {
int socket_type = ((si.type == “stream” ? SOCK_STREAM :
(si.type == “dgram” ? SOCK_DGRAM :
SOCK_SEQPACKET)));
const char* socketcon =
!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();

int s = create_socket(si.name.c_str(), socket_type, si.perm,
si.uid, si.gid, socketcon);
if (s >= 0) {
PublishSocket(si.name, s);
}
}

//2.通过execve执行程序
if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
ERROR(“cannot execve(‘%s’): %s\n”, args_[0].c_str(), strerror(errno));
}

_exit(127);
}

return true;
}

通过注释1和2的代码,我们得知在Start方法中调用fork函数来创建子进程,并在子进程中调用execve执行system/bin/app_process,这样就会进入framework/cmds/app_process/app_main.cpp的main函数,如下所示。
frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{

if (zygote) {
runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);//1
} else if (className) {
runtime.start(“com.android.internal.os.RuntimeInit”, args, zygote);
} else {
fprintf(stderr, “Error: no class name or –zygote supplied.\n”);
app_usage();
LOG_ALWAYS_FATAL(“app_process: no class name or –zygote supplied.”);
return 10;
}
}

从注释1处的代码可以得知调用runtime(AppRuntime)的start来启动zygote。

7.属性服务
Windows平台上有一个注册表管理器,注册表的内容采用键值对的形式来记录用户、软件的一些使用信息。即使系统或者软件重启,它还是能够根据之前在注册表中的记录,进行相应的初始化工作。Android也提供了一个类似的机制,叫做属性服务。
在本文的开始,我们提到在init.cpp代码中和属性服务相关的代码有:
system/core/init/init.cpp

property_init();
start_property_service();

这两句代码用来初始化属性服务配置并启动属性服务。首先我们来学习服务配置的初始化和启动。

属性服务初始化与启动
property_init函数具体实现的代码如下所示。
system/core/init/property_service.cpp

void property_init() {
if (__system_property_area_init()) {
ERROR(“Failed to initialize property area\n”);
exit(1);
}
}

__system_property_area_init函数用来初始化属性内存区域。接下来查看start_property_service函数的具体代码:

void start_property_service() {
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);//1
if (property_set_fd == -1) {
ERROR(“start_property_service socket creation failed: %s\n”, strerror(errno));
exit(1);
}
listen(property_set_fd, 8);//2
register_epoll_handler(property_set_fd, handle_property_set_fd);//3
}

注释1处用来创建非阻塞的socket。注释2处调用listen函数对property_set_fd进行监听,这样创建的socket就成为了server,也就是属性服务;listen函数的第二个参数设置8意味着属性服务*多可以同时为8个试图设置属性的用户提供服务。注释3处的代码将property_set_fd放入了epoll句柄中,用epoll来监听property_set_fd:当property_set_fd中有数据到来时,init进程将用handle_property_set_fd函数进行处理。
在linux新的内核中,epoll用来替换select,epoll*大的好处在于它不会随着监听fd数目的增长而降低效率。因为内核中的select实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。

属性服务处理请求
从上文我们得知,属性服务接收到客户端的请求时,会调用handle_property_set_fd函数进行处理:
system/core/init/property_service.cpp

static void handle_property_set_fd()
{

if(memcmp(msg.name,”ctl.”,4) == 0) {
close(s);
if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR(“sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n”,
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
//检查客户端进程权限
if (check_mac_perms(msg.name, source_ctx, &cr)) {//1
property_set((char*) msg.name, (char*) msg.value);//2
} else {
ERROR(“sys_prop: permission denied uid:%d name:%s\n”,
cr.uid, msg.name);
}
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}

注释1处的代码用来检查客户端进程权限,在注释2处则调用property_set函数对属性进行修改,代码如下所示。

int property_set(const char* name, const char* value) {
int rc = property_set_impl(name, value);
if (rc == -1) {
ERROR(“property_set(\”%s\”, \”%s\”) failed\n”, name, value);
}
return rc;
}

property_set函数主要调用了property_set_impl函数:

static int property_set_impl(const char* name, const char* value) {
size_t namelen = strlen(name);
size_t valuelen = strlen(value);
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;
if (strcmp(“selinux.reload_policy”, name) == 0 && strcmp(“1”, value) == 0) {
if (selinux_reload_policy() != 0) {
ERROR(“Failed to reload policy\n”);
}
} else if (strcmp(“selinux.restorecon_recursive”, name) == 0 && valuelen > 0) {
if (restorecon_recursive(value) != 0) {
ERROR(“Failed to restorecon_recursive %s\n”, value);
}
}
//从属性存储空间查找该属性
prop_info* pi = (prop_info*) __system_property_find(name);
//如果属性存在
if(pi != 0) {
//如果属性以”ro.”开头,则表示是只读,不能修改,直接返回
if(!strncmp(name, “ro.”, 3)) return -1;
//更新属性值
__system_property_update(pi, value, valuelen);
} else {
//如果属性不存在则添加该属性
int rc = __system_property_add(name, namelen, value, valuelen);
if (rc < 0) {
return rc;
}
}
/* If name starts with “net.” treat as a DNS property. */
if (strncmp(“net.”, name, strlen(“net.”)) == 0) {
if (strcmp(“net.change”, name) == 0) {
return 0;
}
//以net.开头的属性名称更新后,需要将属性名称写入net.change中
property_set(“net.change”, name);
} else if (persistent_properties_loaded &&
strncmp(“persist.”, name, strlen(“persist.”)) == 0) {
/*
* Don’t write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
}
property_changed(name, value);
return 0;
}

property_set_impl函数主要用来对属性进行修改,并对以ro、net和persist开头的属性进行相应的处理。到这里,属性服务处理请求的源码就讲到这。

8.init进程总结
讲到这,总结起来init进程主要做了三件事:
1.创建一些文件夹并挂载设备
2.初始化和启动属性服务
3.解析init.rc配置文件并启动zygote进程

Android系统启动流程分析

本文讲解Android系统在启动过程中的关键动作,摈弃特定平台之间的差异,讨论共性的部分,至于启动更加详细的过程,需要结合代码分析,这里给出流程框架,旨在让大家对开机过程更明了。各个平台启动流程基本类似,但代码追踪却有较大区别。高通,MTK,Sprd各有不同处理,均有各自的一套源码,本文代码以展讯平台SC7710系列Android4.1源码进行追踪。

1,Android启动概述
Android系统启动基本可分为3个阶段:Bootloader启动,linux启动,Android启动。

1.1,Bootloader启动
系统引导bootloader(bootable/bootloader/* u-boot/*),加电后,CPU先执行bootloader程序,正常启动系统,加载boot.img,中包含内核。

源码:bootable/bootloader/* , 说明:加电后,CPU将先执行bootloader程序,此处有三种选择:
a: 开机按Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写
b: 开机按Home+Power启动到recovery模式,加载recovery.img,recovery.img包含内核,基本的文件系统,用于工程模式的烧写
c:开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况)
1.2,linux启动
由bootloader加载kernel,kernel经自解压,初始化,载入built-in驱动程序,完成启动。kernel启动后会创建若干内核线程,之后装入并执行程序/sbin/init/,载入init process,切换至user-space。

1.3,Android启动
1.3.1,init进程启动
源码:system/core/init/*
配置文件:system/rootdir/init.rc
说明:init是一个由内核启动的用户级进程,它按照init.rc中的设置执行:启动服务(这里的服务指linux底层服务,如adbd提供adb支持,vold提供SD卡挂载等),执行命令和按其中的配置语句执行相应功能。

1.3.2,zygote服务启动
源码:frameworks/base/cmds/app_main.cpp等。
说明:zygote是一个在init.rc中被指定启动的服务,该服务对应的命令是/system/bin/app_process。
作用:建立Java Runtime,建立虚拟机;建立Socket接收ActivityManangerService的请求,用于Fork应用程序;启动System Server。

1.3.3,systemserver服务启动
源码:frameworks/base/services/java/com/android/server/SystemServer.java
说明:被zygote启动,通过System Manager管理android的服务(这里的服务指frameworks/base/services下的服务,如卫星定位服务,剪切板服务等)。

1.3.4,launcher桌面启动
源码:ActivityManagerService.java为入口,packages/apps/launcher*实现。
说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,*终调用startHomeActivityLocked()启launcher。

1.3.5,lockscreen启动
源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,*终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。

1.3.6,othersapp启动
源码:frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
说明:系统启动成功后SystemServer调用ActivityManagerNative.getDefault().systemReady()通知ActivityManager启动成功,ActivityManager会通过置变量mBooting,通知它的另一线程,该线程会发送广播android.intent.action.BOOT_COMPLETED以告知已注册的第三方程序在开机时自动启动。

2,bootloader启动详细分析
2.1,Bootloader的定义和种类
简单地说,BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适状态,为运行操作系统做好准备。这样描述是比较抽象的,但是它的任务确实不多,终*目标就是把OS拉起来运行。在嵌入式系统世界里存在各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。
先区分一下Bootloader和Monitor[l1] : 严格来说,Bootloader只是引导OS运行起来的代码;而Monitor另外还提供了很多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。在开发过程中Monitor提供了很好地调试功能,不过在开发结束之后,可以完全将其设置成一个Bootloader。所以习惯上将其叫做Bootloader。

%title插图%num

更多bootloader还有:ROLO、Etherboot、ARMboot 、LinuxBIOS等。

对于每种体系结构,都有一系列开放源码Bootloader可以选用:

X86:X86的工作站和服务器上一般使用LILO和GRUB。

ARM:*早有为ARM720处理器开发板所做的固件,又有了armboot,StrongARM平台的blob,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader。

PowerPC:*早使用于ppcboot,不过现在大多数直接使用U-boot。

MIPS:*早都是MIPS开发商自己写的bootloader,不过现在U-boot也支持MIPS架构。

M68K:Redboot能够支持m68k系列的系统。

2.2,Arm特定平台的bootloader
到目前为止,我们公司已经做过多个Arm平台的android方案,包括:marvell(pxa935)、informax(im9815)、mediatek(mt6516/6517)、broadcom(bcm2157)。由于不同处理器芯片厂商对arm core的封装差异比较大,所以不同的arm处理器,对于上电引导都是由特定处理器芯片厂商自己开发的程序,这个上电引导程序通常比较简单,会初始化硬件,提供下载模式等,然后才会加载通常的bootloader。

下面是几个arm平台的bootloader方案:

marvell(pxa935) :                bootROM + OBM [l4] + BLOB

informax(im9815) :             bootROM + barbox + U-boot

mediatek(mt6516/6517) :     bootROM + pre-loader[l5]  + U-boot

broadcom(bcm2157) :          bootROM + boot1/boot2 + U-boot

为了明确U-boot之前的两个loader的作用,下面以broadcom平台为例,看下在上电之后到U-boot的流程,如图1.2.1:

%title插图%num

 

2.3,uboot启动流程详解
*常用的bootloader还是U-boot,可以引导多种操作系统,支持多种架构的CPU。它支持的操作系统有:Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持的CPU架构有:ARM、PowerPC、MISP、X86、NIOS、Xscale等。手机系统不像其他的嵌入式系统,它还需要在启动的过程中关心CP的启动,这个时候就涉及到CP的image和唤醒时刻,而一般的嵌入式系统的uboot只负责引导OS内核。所以这里我们也暂不关心CP的启动,而主要关心AP侧。

从上面第二小节中可以看出,bootloader通常都包含有处理器厂商开发的上电引导程序,不过也不是所有的处理都是这样,比如三星的S3C24X0系列,它的bootROM直接跳到U-boot中执行,首先由bootROM将U-boot的前4KB拷贝到处理器ISRAM,接着在U-boot的前4KB中必须保证要完成的两项主要工作:初始化DDR,nand和nand控制器,接着将U-boot剩余的code拷贝到SDRAM中,然后跳到SDRAM的对应地址上去继续跑U-boot。

所以U-boot的启动过程,大致上可以分成两个阶段:*阶段,汇编代码;第二阶段,c代码。

2.3.1,汇编代码阶段
U-boot的启动由u-boot/arch/arm/cpu/xxx/u-boot.lds开始,其引导调用u-boot/arch/arm/cpu/xxx/start.S。u-boot.lds:

OUTPUT_FORMAT(“elf32-littlearm”, “elf32-littlearm”, “elf32-littlearm”)
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;

. = ALIGN(4);
.text :
{
arch/arm/cpu/arm920t/start.o (.text)//调用对应的start.S,start.o由start.S编译生成
*(.text)
}

. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

. = ALIGN(4);
.data : {
*(.data)
}

. = ALIGN(4);

. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;

. = ALIGN(4);

.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}

.dynsym : {
__dynsym_start = .;
*(.dynsym)
}

.bss __rel_dyn_start (OVERLAY) : {
__bss_start = .;
*(.bss)
. = ALIGN(4);
_end = .;
}

/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
对应的Makefile文件如下:

include $(TOPDIR)/config.mk

LIB = $(obj)lib$(CPU).o

START = start.o

COBJS-y += cpu.o
COBJS-$(CONFIG_USE_IRQ) += interrupts.o

SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS-y) $(SOBJS))
START := $(addprefix $(obj),$(START))

all: $(obj).depend $(START) $(LIB)

$(LIB): $(OBJS)
$(call cmd_link_o_target, $(OBJS))

#########################################################################

# defines $(obj).depend target
include $(SRCTREE)/rules.mk

sinclude $(obj).depend
所以U-boot的*条指令从u-boot/arch/arm/cpu/xxx/start.S文件开始,*阶段主要做了如下事情:

 

(1). 设置CPU进入SVC模式(系统管理模式),cpsr[4:0]=0xd3。

(2). 关中断,INTMSK=0xFFFFFFFF, INTSUBMSK=0x3FF。

(3). 关看门狗,WTCON=0x0。

(4). 调用s3c2410_cache_flush_all函数,使TLBS,I、D Cache,WB中数据失效。

(5). 时钟设置CLKDIVN=0x3 , FCLK:HCLK:PCLK = 1:2:4。

(6). 读取mp15的c1寄存器,将*高两位改成11,表示选择了异步时钟模型。

(7). 检查系统的复位状态,以确定是不是从睡眠唤醒。

#include <asm-offsets.h>
#include <common.h>
#include <config.h>

.globl _start
_start: b start_code
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq

……

//开始的一些初始化操作
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0

bl coloured_LED_init
bl red_LED_on

……

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //重点函数
#endif

/* Set stackpointer in internal RAM to call board_init_f */
/*board.c的board_init_f()函数*/
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f//board初始化

.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */

/* Set up the stack */
stack_setup:
mov sp, r4

adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r2, _TEXT_BASE
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */

copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop

#ifndef CONFIG_PRELOADER
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif

clear_bss:
#ifndef CONFIG_PRELOADER
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
ldr r3, _TEXT_BASE /* Text base */
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */

clbss_l:str r2, [r0] /* clear loop… */
add r0, r0, #4
cmp r0, r1
bne clbss_l

bl coloured_LED_init
bl red_LED_on
#endif

/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
ldr r0, _nand_boot_ofs
mov pc, r0

_nand_boot_ofs:
.word nand_boot
#else
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it … */
mov pc, lr
/*board_init_r 此处走至u-boot\arch\arm\lib\board.c的board_init_r()函数 */
_board_init_r_ofs:
.word board_init_r – _start
#endif
/*至此走至C代码的阶段*/
_rel_dyn_start_ofs:
.word __rel_dyn_start – _start
_rel_dyn_end_ofs:
.word __rel_dyn_end – _start
_dynsym_start_ofs:
.word __dynsym_start – _start

……

#endif
根据这几条语句来判断系统是从nand启动的还是直接将程序下载到SDRAM中运行的,这里涉及到运行时域 和位置无关代码的概念,ldr r0,_TEXT_BASE的作用是将config.mk文件中定义的TEXT_BASE值(0x33f80000)装载到r0中,adr r1,_start该指令是条伪指令,在编译的时候会被转换成ADD或SUB指令根据当前pc值计算出_start标号的地址,这样的话就可以知道当前程序在什么地址运行(位置无关代码:做成程序的所有指令都是相对寻址的指令,包括跳转指令等,这样代码就可以不在链接所指定的地址上运行)。在上电之后,系统从nand启动,这里得到r0和r1值是不一样的,r0=0x33f80000,而r1=0x00000000。所以接下来会执行cpu_init_crit函数。

cpu_init_crit函数,主要完成了两个工作:首先使ICache and Dcache,TLBs中早期内容失效,再设置p15 control register c1,关闭MMU,Dcache,但是打开了Icache和Fault checking,(要求mmu和Dcache是必须要关闭的,而Icache可以打开可以关闭);其次调用/board/nextdvr2410/memsetup.S文件中的memsetup函数来建立对SDRAM的访问时序。

Relocate函数,加载nand flash中的uboot到SDRAM中,代码会加载到0x33f80000开始的地址,空间大小是512。

//这里参考的是展讯平台7710的源代码,所以并无start_armboot函数,取而代之的是board_init_r函数。请知悉。

ldr pc, _start_armboot

_start_armboot: .word start_armboot

这里将会进入第二阶段的c代码部分:board_init_r()函数,/u-boot/arch/arm/lib/board.c。

2.3.2,C代码阶段
先看/u-boot/arch/arm/lib/board.c的board_init_r()函数:

void board_init_r (gd_t *id, ulong dest_addr)
{
……
/**一系列初始化操作之后 重点为do_cboot(NULL, 0, 1, NULL)和main_loop ()*/
board_init(); /* Setup chipselects */

boot_pwr_check();

#ifdef CONFIG_SERIAL_MULTI
serial_initialize();
#endif

debug (“Now running in RAM – U-Boot at: %08lx\n”, dest_addr);

#ifdef CONFIG_LOGBUFFER
logbuff_init_ptrs ();
#endif
#ifdef CONFIG_POST
post_output_backlog ();
#endif

/* The Malloc area is immediately below the monitor copy in DRAM */
malloc_start = dest_addr – TOTAL_MALLOC_LEN;
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(4);
#endif
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(5);
#endif
boot_pwr_check();

#if !defined(CONFIG_SYS_NO_FLASH)
puts (“FLASH: “);

if ((flash_size = flash_init ()) > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
print_size (flash_size, “”);
/*
* Compute and print flash CRC if flashchecksum is set to ‘y’
*
* NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
*/
s = getenv (“flashchecksum”);
if (s && (*s == ‘y’)) {
printf (” CRC: %08X”,
crc32 (0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)
);
}
putc (‘\n’);
# else /* !CONFIG_SYS_FLASH_CHECKSUM */
print_size (flash_size, “\n”);
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
} else {
puts (failed);
hang ();
}
#endif
boot_pwr_check();

#if !defined(CONFIG_EMMC_BOOT)
#if defined(CONFIG_CMD_NAND)
puts (“NAND: “);
ret = nand_init(); /* go init the NAND */
if (ret) {
puts (“NAND init error “);
while(1);
}
#endif
#endif

boot_pwr_check();
#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(6);
#endif

#if defined(CONFIG_CMD_ONENAND)
#if !(defined CONFIG_TIGER && defined CONFIG_EMMC_BOOT)
onenand_init();
#endif
#endif

#ifdef CONFIG_GENERIC_MMC
puts(“MMC: “);
mmc_initialize(bd);
#endif

#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif

#ifdef CONFIG_EMMC_BOOT
mmc_legacy_init(1);
#endif
/* initialize environment */
env_relocate ();
boot_pwr_check();

#ifdef CONFIG_VFD
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */

/*tempaily use for tiger to avoid died as refreshing LCD*/

/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr (“ipaddr”);

stdio_init (); /* get the devices list going. */
boot_pwr_check();

jumptable_init ();
boot_pwr_check();

#if defined(CONFIG_API)
/* Initialize API */
api_init ();
#endif
char fake[4]=”fak”;
setenv(“splashimage”, fake);

console_init_r (); /* fully init console as a device */
boot_pwr_check();

#if defined(CONFIG_ARCH_MISC_INIT)
/* miscellaneous arch dependent initialisations */
arch_misc_init ();
#endif
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif

/* set up exceptions */
interrupt_init ();
/* enable exceptions */
enable_interrupts ();
boot_pwr_check();

/* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
/* XXX: this needs to be moved to board init */
if (getenv (“ethaddr”)) {
uchar enetaddr[6];
eth_getenv_enetaddr(“ethaddr”, enetaddr);
smc_set_mac_addr(enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

/* Initialize from environment */
if ((s = getenv (“loadaddr”)) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv (“bootfile”)) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
boot_pwr_check();
//usb_eth_initialize(NULL);
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
……

#ifdef SPRD_EVM_TAG_ON
SPRD_EVM_TAG(11);
#endif
extern int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);
boot_pwr_check();

do_cboot(NULL, 0, 1, NULL);//重点操作函数,此处走至u-boot\property\cmd_cboot.c
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}

/* NOTREACHED – no way out of command loop except booting */
}

该段代码完成了一些设备的初始化do_cboot(NULL, 0, 1, NULL)和main_loop ()是此处重点函数,其中do_cboot(NULL, 0, 1, NULL)的实现在u-boot/property/cmd_cboot.c而main_loop()则是在u-boot/common/main.c中。先看u-boot/property/cmd_cboot.c。

int boot_pwr_check(void)
{
static int total_cnt = 0;
if(!power_button_pressed())
total_cnt ++;
return total_cnt;
}
#define mdelay(_ms) udelay(_ms*1000)

int do_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
uint32_t key_mode = 0;
uint32_t key_code = 0;
volatile int i;

if(argc > 2)
goto usage;

#ifdef CONFIG_SC8830
if(cali_file_check())
calibration_detect(2);
#endif
#ifdef CONFIG_SC7710G2
{
extern void set_cp_emc_pad(void);
set_cp_emc_pad();
}
#endif
CHG_Init();

#ifdef CONFIG_SC8830
DCDC_Cal_ArmCore();
//DCDC_Cal_All(0);
#endif

#ifdef CONFIG_AUTOBOOT
normal_mode();//如果down的是autopoweron的uboot,这里会直接去正常开机
#endif

#ifdef CONFIG_SC7710G2
if(!pbint2_connected())
normal_mode();
#endif

boot_pwr_check();

#ifdef CONFIG_SC8800G
CHG_ShutDown();
if(charger_connected()){
mdelay(10);
CHG_TurnOn();
}else{
//根据sp8810.h里的LOW_BAT_VOL,如果电压低于3.5V,则直接power down
if(is_bat_low()){
printf(“shut down again for low battery\n”);
power_down_devices();
while(1)
;
}
}
#else

#ifndef CONFIG_MACH_CORI
if(is_bat_low()){
printf(“shut down again for low battery\n”);
mdelay(10000);
power_down_devices();
while(1)
;
}
#endif
#endif

boot_pwr_check();
board_keypad_init();//初始化键盘
boot_pwr_check();

#ifdef CONFIG_SPRD_SYSDUMP
write_sysdump_before_boot();
#endif

int recovery_init(void);
int ret =0;
ret = recovery_init();
if(ret == 1){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
recovery_mode_without_update();
}else if(ret == 2){
#ifndef CONFIG_SC8830
try_update_modem(); //update img from mmc
#endif
normal_mode();
}

unsigned check_reboot_mode(void);
//获取寄存器里HW的rest标志位,得到当前的开机模式
//此处主要是异常重启,恢复出厂设置,关机闹钟等(没有按power键导致的开机)
unsigned rst_mode= check_reboot_mode();
//检查是否是recovery模式
if(rst_mode == RECOVERY_MODE){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
recovery_mode();
}
else if(rst_mode == FASTBOOT_MODE){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
fastboot_mode();
}else if(rst_mode == NORMAL_MODE){
normal_mode();
}else if(rst_mode == WATCHDOG_REBOOT){
watchdog_mode();
}else if(rst_mode == UNKNOW_REBOOT_MODE){
unknow_reboot_mode();
}else if(rst_mode == PANIC_REBOOT){
panic_reboot_mode();
}else if(rst_mode == ALARM_MODE){
int flag =alarm_flag_check();
if(flag == 1)
alarm_mode();
else if(flag == 2)
normal_mode();
}else if(rst_mode == SLEEP_MODE){
sleep_mode();
}else if(rst_mode == SPECIAL_MODE){
special_mode();
}else if(rst_mode == CALIBRATION_MODE){
calibration_detect(0);
}
#ifdef CONFIG_SC8810
// normal_mode();
#endif
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(charger_connected()){
DBG(“%s: charger connected\n”, __FUNCTION__);
#if defined (CONFIG_SP8810W) || defined(CONFIG_SC7710G2)
calibration_detect(1);
#endif
charge_mode();
}
//find the power up trigger
//如果按power键的“次数”达标了,认为这个是一次长按事件
else if(boot_pwr_check() >= get_pwr_key_cnt()){
DBG(“%s: power button press\n”, __FUNCTION__);
DBG(“boot_pwr_check=%d,get_pwr_key_cnt=%d\n”,boot_pwr_check(),get_pwr_key_cnt());
//go on to check other keys
mdelay(50);
for(i=0; i<10;i++){
key_code = board_key_scan();//获取另外一个按键
if(key_code != KEY_RESERVED)
break;
}
DBG(“key_code %d\n”, key_code);
//查找对应的按键码对应的开机模式
key_mode = check_key_boot(key_code);

switch(key_mode){
case BOOT_FASTBOOT:
fastboot_mode();
break;
case BOOT_RECOVERY:
recovery_mode();
break;
case BOOT_CALIBRATE:
engtest_mode();
return 0; //back to normal boot
break;
case BOOT_DLOADER:
dloader_mode();
break;
default:
break;//如果是正常开机模式,因为没有
}
}
else if(alarm_triggered() && alarm_flag_check()){
DBG(“%s: alarm triggered\n”, __FUNCTION__);
int flag =alarm_flag_check();

if(flag == 1){
//如果是闹钟触发导致的开机,则进入关机闹钟模式
alarm_mode();
}
else if(flag == 2){
normal_mode();//如果只按了power键。
}

}else{
#if BOOT_NATIVE_LINUX_MODEM
*(volatile u32*)CALIBRATION_FLAG = 0xca;
#endif
#if !defined (CONFIG_SC8830) && !defined(CONFIG_SC7710G2)
calibration_detect(0);
#endif
//if calibrate success, it will here
DBG(“%s: power done again\n”, __FUNCTION__);
power_down_devices();
while(1)
;
}

if(argc == 1){
DBG(“func: %s line: %d\n”, __func__, __LINE__);
normal_mode();
return 1;
}

if(argc == 2){
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”normal”) == 0){
normal_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”recovery”) == 0){
recovery_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”fastboot”) == 0){
fastboot_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”dloader”) == 0){
dloader_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”charge”) == 0){
//如果没有按power键,且插入了充电器,则进入充电模式
charge_mode();
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

if(strcmp(argv[1],”caliberation”) == 0){
calibration_detect(1);
return 1;
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);
}
DBG(“func: %s line: %d\n”, __func__, __LINE__);

usage:
cmd_usage(cmdtp);
return 1;
}
接下来分析正常开机的流程也就是normal_mode(),其他几种开机流程与normal_mode()类似,不再一一分析。android共提供了多种mode:

normal_mode()的实现在 u-boot/property/normal_mode.c:

void normal_mode(void)
{
#if defined (CONFIG_SC8810) || defined (CONFIG_SC8825) || defined (CONFIG_SC8830)
//MMU_Init(CONFIG_MMU_TABLE_ADDR);
vibrator_hw_init();//初始化马达
#endif
set_vibrator(1);//起震,这个就是开机震的那一下

#ifndef UART_CONSOLE_SUPPORT
#ifdef CONFIG_SC7710G2
extern int serial1_SwitchToModem(void);
serial1_SwitchToModem();
#endif
#endif

#if BOOT_NATIVE_LINUX
vlx_nand_boot(BOOT_PART, CONFIG_BOOTARGS, BACKLIGHT_ON);
#else
vlx_nand_boot(BOOT_PART, NULL, BACKLIGHT_ON);
#endif

}

*终将操作交给vlx_nand_boot(),其实现在u-boot/property/normal_nand_mode.c

void vlx_nand_boot(char * kernel_pname, char * cmdline, int backlight_set)
{
……
char *fixnvpoint = “/fixnv”;
char *fixnvfilename = “/fixnv/fixnv.bin”;
char *fixnvfilename2 = “/fixnv/fixnvchange.bin”;
char *backupfixnvpoint = “/backupfixnv”;
char *backupfixnvfilename = “/backupfixnv/fixnv.bin”;

char *runtimenvpoint = “/runtimenv”;
char *runtimenvpoint2 = “/runtimenv”;
char *runtimenvfilename = “/runtimenv/runtimenv.bin”;
char *runtimenvfilename2 = “/runtimenv/runtimenvbkup.bin”;

char *productinfopoint = “/productinfo”;
char *productinfofilename = “/productinfo/productinfo.bin”;
char *productinfofilename2 = “/productinfo/productinfobkup.bin”;

int orginal_right, backupfile_right;
unsigned long orginal_index, backupfile_index;
nand_erase_options_t opts;
char * mtdpart_def = NULL;
#if (defined CONFIG_SC8810) || (defined CONFIG_SC8825)
MMU_Init(CONFIG_MMU_TABLE_ADDR);
#endif
ret = mtdparts_init();
if (ret != 0){
printf(“mtdparts init error %d\n”, ret);
return;
}

#ifdef CONFIG_SPLASH_SCREEN
#define SPLASH_PART “boot_logo”
ret = find_dev_and_part(SPLASH_PART, &dev, &pnum, &part);
if(ret){
printf(“No partition named %s\n”, SPLASH_PART);
return;
}else if(dev->id->type != MTD_DEV_TYPE_NAND){
printf(“Partition %s not a NAND device\n”, SPLASH_PART);
return;
}
//读取下载到nand中的boot_logo,就是开机亮的那一屏
off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
size = 1<<19;//where the size come from????//和dowload工具中的地址一致
char * bmp_img = malloc(size);
if(!bmp_img){
printf(“not enough memory for splash image\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
//*次LCD logo
lcd_display_logo(backlight_set,(ulong)bmp_img,size);
#endif
set_vibrator(0);//停止震动,如果发现开机狂震不止,那就是没走到这里。
{
nand_block_info(nand, &good_blknum, &bad_blknum);
printf(“good is %d bad is %d\n”, good_blknum, bad_blknum);
}

ret = load_sector_to_memory(fixnvpoint,
fixnvfilename2,
fixnvfilename,
(unsigned char *)FIXNV_ADR,
(unsigned char *)MODEM_ADR,
FIXNV_SIZE + 4);
……

#elif defined(CONFIG_CALIBRATION_MODE_NEW)
#if defined(CONFIG_SP7702) || defined(CONFIG_SP8810W)
/*
force dsp sleep in native 8810 verson to reduce power consumption
*/
extern void DSP_ForceSleep(void);
DSP_ForceSleep();
printf(“dsp nand read ok1 %d\n”, ret);
#endif

#ifdef CONFIG_SC7710G2
ret = try_update_spl();
if(ret == -1){
printf(“try update spl faild!\n”);
return -1;
}

ret = try_load_fixnv();
if(ret == -1){
printf(“try load fixnv faild!\n”);
return -1;
}

ret = try_load_runtimenv();
if(ret == -1){
printf(“try load runtimenv faild!\n”);
}

ret = try_load_productinfo();
if(ret == -1){
printf(“try load productinfo faild!\n”);
}
#endif
if(poweron_by_calibration())
{
#ifndef CONFIG_SC7710G2
// ———————fix nv——————————–

……

// ———————runtime nv—————————-
……
// ———————DSP —————————-
……

#endif

/* KERNEL_PART */
printf(“Reading kernel to 0x%08x\n”, KERNEL_ADR);

ret = find_dev_and_part(kernel_pname, &dev, &pnum, &part);
if(ret){
printf(“No partition named %s\n”, kernel_pname);
return;
}else if(dev->id->type != MTD_DEV_TYPE_NAND){
printf(“Partition %s not a NAND device\n”, kernel_pname);
return;
}

off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
#if 0
size = nand->writesize;
flash_page_size = nand->writesize;
ret = nand_read_offset_ret(nand, off, &size, (void *)hdr, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
if(memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)){
printf(“bad boot image header, give up read!!!!\n”);
return;
}
else
{
//read kernel image
size = (hdr->kernel_size+(flash_page_size – 1)) & (~(flash_page_size – 1));
if(size <=0){
printf(“kernel image should not be zero\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)KERNEL_ADR, &off);
if(ret != 0){
printf(“kernel nand read error %d\n”, ret);
return;
}
//read ramdisk image
size = (hdr->ramdisk_size+(flash_page_size – 1)) & (~(flash_page_size – 1));
if(size<0){
printf(“ramdisk size error\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)RAMDISK_ADR, &off);
if(ret != 0){
printf(“ramdisk nand read error %d\n”, ret);
return;
}
}
#else

ret = load_kernel_and_layout(nand,
(unsigned int)off,
(char *)raw_header,
(char *) KERNEL_ADR,
(char *) RAMDISK_ADR,
2048,
nand->writesize);

if (ret != 0) {
printf(“ramdisk nand read error %d\n”, ret);
return;
}

#endif

……

{

good_blknum = 0;
bad_blknum = 0;
nand_block_info(nand, &good_blknum, &bad_blknum);
printf(“good is %d bad is %d\n”, good_blknum, bad_blknum);
}
creat_cmdline(cmdline,hdr);
vlx_entry();//末尾进入entry(),其实现在normal_mode.c
}
该函数的重点在开头和结尾的相关操作,开头部分见注释,重点分析vlx_entry()函数,其实现在normal_mode.c:

void vlx_entry()
{
#if !(defined CONFIG_SC8810 || defined CONFIG_TIGER || defined CONFIG_SC8830)
MMU_InvalideICACHEALL();
#endif

#if (defined CONFIG_SC8810) || (defined CONFIG_SC8825) || (defined CONFIG_SC8830)
MMU_DisableIDCM();
#endif

#ifdef REBOOT_FUNCTION_INUBOOT
reboot_func();
#endif

#if BOOT_NATIVE_LINUX
start_linux();
#else
void (*entry)(void) = (void*) VMJALUNA_ADR;
entry();
#endif
}
这里的entry()跳转到VM虚拟机的首地址,start_linux()则是进入kernel的方法,仍在normal_mode.c中实现:

static int start_linux()
{
void (*theKernel)(int zero, int arch, u32 params);
u32 exec_at = (u32)-1;
u32 parm_at = (u32)-1;
u32 machine_type;

machine_type = machine_arch_type; /* get machine type */
//重点根据KERNEL的地址
theKernel = (void (*)(int, int, u32))KERNEL_ADR; /* set the kernel address */
#ifndef CONFIG_SC8830
*(volatile u32*)0x84001000 = ‘j’;
*(volatile u32*)0x84001000 = ‘m’;
*(volatile u32*)0x84001000 = ‘p’;
#endif
//根据Kernel的地址走至Kernel\init\main.c的start_kernel()
theKernel(0, machine_type, VLX_TAG_ADDR); /* jump to kernel with register set */
while(1);
return 0;
}

至此,已经到了Kernel\init\main.c的start_kernel(),即来到了linux的世界。

3,linux启动详细分析
Kernel\init\main.c的start_kernel()的kernel的起点,先看这个函数:

asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];

#ifdef CONFIG_NKERNEL
jiffies_64 = INITIAL_JIFFIES;
#endif
smp_setup_processor_id();

/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();

/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();

cgroup_init_early();

local_irq_disable();
early_boot_irqs_disabled = true;

/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE “%s”, linux_banner);
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

build_all_zonelists(NULL);
page_alloc_init();

printk(KERN_NOTICE “Kernel command line: %s\n”, boot_command_line);
parse_early_param();
parse_args(“Booting kernel”, static_command_line, __start___param,
__stop___param – __start___param,
&unknown_bootoption);
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();

/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time – but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption – early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (!irqs_disabled()) {
printk(KERN_WARNING “start_kernel(): bug: interrupts were ”
“enabled *very* early, fixing it\n”);
local_irq_disable();
}
idr_init_cache();
perf_event_init();
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
call_function_init();
if (!irqs_disabled())
printk(KERN_CRIT “start_kernel(): bug: interrupts were ”
“enabled early\n”);
early_boot_irqs_disabled = false;
local_irq_enable();

/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;

kmem_cache_init_late();

/*
* HACK ALERT! This is early. We’re enabling the console before
* we’ve done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic(panic_later, panic_param);

lockdep_info();

/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_CRIT “initrd overwritten (0x%08lx < 0x%08lx) – ”
“disabling it.\n”,
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
enable_debug_pagealloc();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();

check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();

ftrace_init();

/* Do the rest non-__init’ed, we’re now alive */
//*个跟init 进程相关的函数
rest_init();
}
该函数所调用的大部分都是相关的初始化操作,而跟启动关联的是结尾的rest_init() ,该函数是*个跟init进程相关的函数,看其实现:

static noinline void __init_refok rest_init(void)
{
int pid;

rcu_scheduler_starting();
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
//启动kernel_init来进行接下来的初始化
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);

/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();

/* Call into cpu_idle with preempt disabled */
cpu_idle();//将系统交给调度器处理。
}
该函数启动了kernel_init来进行后续的初始化,进而看kernel_init(),这些函数任然在main.c中实现。

static int __init kernel_init(void * unused)
{
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_HIGH_MEMORY]);
/*
* init can run on any cpu.
*/
set_cpus_allowed_ptr(current, cpu_all_mask);

cad_pid = task_pid(current);

smp_prepare_cpus(setup_max_cpus);

do_pre_smp_initcalls();
lockup_detector_init();

smp_init();
sched_init_smp();
//此函数中会调用各个驱动模块的加载函数(静态编译的,非ko)来初始化设备
do_basic_setup();

/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) “/dev/console”, O_RDWR, 0) < 0)
printk(KERN_WARNING “Warning: unable to open an initial console.\n”);

(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/

if (!ramdisk_execute_command)
ramdisk_execute_command = “/init”;

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

/*
* Ok, we have completed the initial bootup, and
* we’re essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
//走至init 进程的相关操作
init_post();
return 0;
}

init进程由init_post()创建 即main.c 的init_post():
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();

current->signal->flags |= SIGNAL_UNKILLABLE;

if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING “Failed to execute %s\n”,
ramdisk_execute_command);
}

/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
//至此init启动完成,接下来的启动就是System\core\init\init.c的main()
run_init_process(execute_command);
printk(KERN_WARNING “Failed to execute %s. Attempting ”
“defaults…\n”, execute_command);
}
run_init_process(“/sbin/init”);
run_init_process(“/etc/init”);
run_init_process(“/bin/init”);
run_init_process(“/bin/sh”);

panic(“No init found. Try passing init= option to kernel. ”
“See Linux Documentation/init.txt for guidance.”);
}
4,android启动详细分析
android部分的启动包括几个部分:init,zygote,systemserver,launcher,lockscreen,othersapps。

4.1,init启动
init是一个进程,确切的说,是linux系统用户空间的*个进程,android是基于linux 的,所以init也是android用户空间的*个进程,他的进程号是1,作为天字*号进程,其有很多重要的职责。其*重要的职责是创建了Zygote以及提供了systemserver。system\core\init\init.c的入口函数是main()。

int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;

if (!strcmp(basename(argv[0]), “ueventd”))
return ueventd_main(argc, argv);

/* clear the umask */
umask(0);

/* Get the basic filesystem setup we need put
* together in the initramdisk on / and then we’ll
* let the rc file figure out the rest.
*/
//创建一些文件夹,并挂载设备,这些是与linux相关的
mkdir(“/dev”, 0755);
mkdir(“/proc”, 0755);
mkdir(“/sys”, 0755);

mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
mount(“proc”, “/proc”, “proc”, 0, NULL);
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);

/* indicate that booting is in progress to background fw loaders, etc */
close(open(“/dev/.booting”, O_WRONLY | O_CREAT, 0000));

/* We must have some place other than / to create the
* device nodes for kmsg and null, otherwise we won’t
* be able to remount / read-only later on.
* Now that tmpfs is mounted on /dev, we can actually
* talk to the outside world.
*/
//重定向标准输入输出 错误输出到/dev/_null_
open_devnull_stdio();
//设置init的日志输出设备为/dev/_kmsg_,不过该文件打开后
//会立刻被unlink,这样其他进程就无法打开这个文件读取日志信息
klog_init();
//prop配置文件的解析与初始化操作,如//设置”/default.prop”属性文件
property_init();
//通过读取proc/cpuinfo得到机器的hardware名
get_hardware_name(hardware, &revision);

process_kernel_cmdline();

#ifdef HAVE_SELINUX
INFO(“loading selinux policy\n”);
selinux_load_policy();
#endif

is_charger = !strcmp(bootmode, “charger”);

INFO(“property init\n”);
if (!is_charger)
property_load_boot_defaults();

INFO(“reading config file\n”);
//解析init.rc配置文件 非常重要,文件系统的挂载,权限设置
//以及系统server的启动,包括Zygote的创建
init_parse_config_file(“/init.rc”);
/**
**解析完init.rc 会得到一系列的action动作
**keychord_init_action和console_init_action
**/
action_for_each_trigger(“early-init”, action_add_queue_tail);

queue_builtin_action(wait_for_coldboot_done_action, “wait_for_coldboot_done”);
queue_builtin_action(keychord_init_action, “keychord_init”);
//console_init_action为控制台初始化,此处会加载一帧boot logo文件为initlogo.rle
queue_builtin_action(console_init_action, “console_init”);

/* execute all the boot actions to get us started */
action_for_each_trigger(“init”, action_add_queue_tail);

/* skip mounting filesystems in charger mode */

action_for_each_trigger(“early-fs”, action_add_queue_tail);
//action_for_each_trigger(“fs”, action_add_queue_tail);
{
bool has_3partions = false;

has_3partions = (!access(“/sys/block/mmcblk0/mmcblk0p3”,R_OK))
&& (!access(“/sys/block/mmcblk0/mmcblk0p2”,R_OK))
&& (!access(“/sys/block/mmcblk0/mmcblk0p1”,R_OK));

if (has_3partions) {
action_for_each_trigger(“fs-two”, action_add_queue_tail);
} else {
action_for_each_trigger(“fs”, action_add_queue_tail);
}
}
action_for_each_trigger(“post-fs”, action_add_queue_tail);
if (!is_charger) {
//action_for_each_trigger(“post-fs”, action_add_queue_tail);
action_for_each_trigger(“post-fs-data”, action_add_queue_tail);
}

queue_builtin_action(property_service_init_action, “property_service_init”);
queue_builtin_action(signal_init_action, “signal_init”);
queue_builtin_action(check_startup_action, “check_startup”);

if (!strcmp(bootmode, “alarm”)) {
action_for_each_trigger(“alarm”, action_add_queue_tail);
}
if (is_charger) {
action_for_each_trigger(“charger”, action_add_queue_tail);
} else {
action_for_each_trigger(“early-boot”, action_add_queue_tail);
action_for_each_trigger(“boot”, action_add_queue_tail);
}

/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”);

#if BOOTCHART
queue_builtin_action(bootchart_init_action, “bootchart_init”);
#endif

for(;;) {//无限循环启动进程
int nr, i, timeout = -1;

execute_one_command();//再循环中执行动作
restart_processes();//重启已经死去的进程

if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}

if (process_needs_restart) {
timeout = (process_needs_restart – gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

if (!action_queue_empty() || cur_action)
timeout = 0;

#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || –bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif

nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;

for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}

return 0;
}
从以上代码可知,init的工作任务还是很重的,上面的代码已经省略的不少,但任然很多,不过分析两个知识点来看,可将init的工作流程精简为四点:1,解析配置文件重点是init.rc。2,执行各个阶段的动作,创建zygote的工作就在其中的某一个阶段完成。3,调用property_init()初始化属性相关的资源,并且通过property_load_boot_defaults()启动属性服务。4,init进入一个无限循环,并且等待一些事情的发生。接下来重点看下解析配置文件的init.rc。解析函数:

int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;

parse_config(fn, data);
DUMP();
return 0;
}
再看init.rc文件:

……

// service管理器 —- > servicemanager.cpp/ servicemanager.java
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote —— > 启动zygote进程
onrestart restart media —— > 启动media
onrestart restart surfaceflinger—— > 启动surfaceflinger
onrestart restart drm—— > 启动drm
service vold /system/bin/vold
class core
socket vold stream 0660 root mount
ioprio be 2
service netd /system/bin/netd
class main
socket netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
service debuggerd /system/bin/debuggerd
class main
service ril-daemon /system/bin/rild
class main
socket rild stream 660 root radio
socket rild-debug stream 660 radio system
user root
group radio cache inet misc audio sdcard_rw log
// surfaceflinger服务
//对应surfaceflinger.cpp—– >在system_server中具体实现
service surfaceflinger /system/bin/surfaceflinger
class main
user system
group graphics
onrestart restart zygote
// zygote进程
//后面重点分析
service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
// drm服务
service drm /system/bin/drmserver
class main
user drm
group system inet drmrpc
// mediaserver服务
//在Main_mediaserver.cpp中实现,启动audio camera 等服务
service media /system/bin/mediaserver
class main
user media
group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc
ioprio rt 4
//此处引导播放开机动画,并在surfaceflinger中具体实现
service bootanim /system/bin/bootanimation
class main
user graphics
group graphics
disabled
oneshot
……
在init.rc中完成了一系列的重要操作:文件系统权限及挂载,启动zygote,启动系统服务,播放开机动画。当然如何解析对应的代码,并完成对应的操作,如启动zygote、播放开机动画,可以参考相关资料或查看源码,此处不再详述。至此init已经将部分操作交给了zygote。
4.2,zygote启动
zygote的启动预示着真正的来到了java的世界。zygote这个词的中文意思的受精卵,他和android系统中的java世界有着重要关系。zygote本身是一个native的应用程序,与驱动,内核均无关系。根据对init的了解我们知道,zygote是有init进程根据init.rc文件中的配置项创建的。先分析其来历,zygote*初的名字叫app_process,这个名字是在android.mk文件中指定的。但在运行过程中,app_process通过linux下的pctrl系统调用将自己的名字换成了zygote,所以通过进程看到的名称是zygote。

Zygote进程中完成了java虚拟机的创建及初始化,以及准备了java运行时环境,还有jni的准备工作,所以zygote占据了整个android世界的半壁江山,另半壁江山则是system_server,后续会详细介绍。

Zygote—- >入口文件App_main.cpp —- >main()
Zygote原意是受精卵的意思。
在linux中指app_process即:frameworks/base/cmds/app_process目录下的App_main.cpp
此处可发现main()

int main(int argc, const char* const argv[])
{
// These are global variables in ProcessState.cpp
mArgC = argc;
mArgV = argv;

mArgLen = 0;
for (int i=0; i<argc; i++) {
mArgLen += strlen(argv[i]) + 1;
}
mArgLen–;

AppRuntime runtime;
const char* argv0 = argv[0];

// Process command line arguments
// ignore argv[0]
argc–;
argv++;

// Everything up to ‘–‘ or first non ‘-‘ arg goes to the vm

int i = runtime.addVmArguments(argc, argv);

// Parse runtime arguments. Stop at first unrecognized option.
bool zygote = false;
bool startSystemServer = false;
bool application = false;
const char* parentDir = NULL;
const char* niceName = NULL;
const char* className = NULL;
while (i < argc) {
const char* arg = argv[i++];
if (!parentDir) {
parentDir = arg;
} else if (strcmp(arg, “–zygote”) == 0) {
zygote = true;
niceName = “zygote”;
} else if (strcmp(arg, “–start-system-server”) == 0) {
startSystemServer = true;
} else if (strcmp(arg, “–application”) == 0) {
application = true;
} else if (strncmp(arg, “–nice-name=”, 12) == 0) {
niceName = arg + 12;
} else {
className = arg;
break;
}
}

if (niceName && *niceName) {
setArgv0(argv0, niceName);
set_process_name(niceName);
}

runtime.mParentDir = parentDir;

if (zygote) {
// do last shutdown check
ALOGV(“doLastShutDownCheck”);
doLastShutDownCheck();
runtime.start(“com.android.internal.os.ZygoteInit”,
startSystemServer ? “start-system-server” : “”);
} else if (className) {
// Remainder of args get passed to startup class main()
runtime.mClassName = className;
runtime.mArgC = argc – i;
runtime.mArgV = argv + i;
runtime.start(“com.android.internal.os.RuntimeInit”,
application ? “application” : “tool”);
} else {
fprintf(stderr, “Error: no class name or –zygote supplied.\n”);
app_usage();
LOG_ALWAYS_FATAL(“app_process: no class name or –zygote supplied.”);
return 10;
}
}

该代码主要完成工作如下:

1,niceName = “zygote”;—- >重命名,原进程名称为app_process
,2,setArgv0(argv0, niceName);
,3,set_process_name(niceName); —- >完成重命名操作
,4,AppRuntime runtime;—– >App_main.cpp的一个内部类,其继承AndroidRuntime.cpp
,5,runtime.start(“com.android.internal.os.ZygoteInit”,startSystemServer(“startsystemserver”));

备注:AppRuntime 作为一个内部类,在main()里调用。其完成:
1, getClassName() —- >运行时文件类名
2, onVmCreated()—- >java虚拟机创建
3, onStarted()—- >调用时加载
4, onZygoteInit()—- >初始化虚拟机
5, onExit()—- >退出时的操作 ——— > 上述函数基本自动调用

接着,走进runtime.start(com.android.internal.os.ZygoteInit)。runtime来自AndroidRuntime.cpp。AndroidRuntime.cpp—— >AndroidRuntime::start(const char* className, const char* options)。frameworks\base\core\jni\AndroidRuntime.cpp。分析其start()函数:

void AndroidRuntime::start(const char* className, const char* options)
{
ALOGD(“\n>>>>>> AndroidRuntime START %s <<<<<<\n”,
className != NULL ? className : “(unknown)”);

blockSigpipe();

/*
* ‘startSystemServer == true’ means runtime is obsolete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
if (strcmp(options, “start-system-server”) == 0) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,
ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}

const char* rootDir = getenv(“ANDROID_ROOT”);
if (rootDir == NULL) {
rootDir = “/system”;
if (!hasDir(“/system”)) {
LOG_FATAL(“No root directory specified, and /android does not exist.”);
return;
}
setenv(“ANDROID_ROOT”, rootDir, 1);
}

//const char* kernelHack = getenv(“LD_ASSUME_KERNEL”);
//ALOGD(“Found LD_ASSUME_KERNEL=’%s’\n”, kernelHack);

/* start the virtual machine */
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
onVmCreated(env);

/*
* Register android functions.
*/
if (startReg(env) < 0) {
ALOGE(“Unable to register all android natives\n”);
return;
}

/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
jstring optionsStr;

stringClass = env->FindClass(“java/lang/String”);
assert(stringClass != NULL);
strArray = env->NewObjectArray(2, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
optionsStr = env->NewStringUTF(options);
env->SetObjectArrayElement(strArray, 1, optionsStr);

/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE(“JavaVM unable to locate class ‘%s’\n”, slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, “main”,
“([Ljava/lang/String;)V”);
if (startMeth == NULL) {
ALOGE(“JavaVM unable to find main() in ‘%s’\n”, className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
free(slashClassName);

ALOGD(“Shutting down VM\n”);
if (mJavaVM->DetachCurrentThread() != JNI_OK)
ALOGW(“Warning: unable to detach main thread\n”);
if (mJavaVM->DestroyJavaVM() != 0)
ALOGW(“Warning: VM did not shut down cleanly\n”);
}

该函数完成操作:
1, onVmCreated(env);—– >创建虚拟机
2, JNIEnv* env; —- > JNI环境的初始化
3, env->CallStaticVoidMethod(startClass, startMeth, strArray); —– >*终函数与上述步骤中的runtime.start(com.android.internal.os.ZygoteInit)对应。
4, 至此走到—- ZygoteInit.java—-main()

ZygoteInit.java —main()—– >java世界准备已经完成,欢迎来到java世界。

public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();

registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());

// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup
gc();

// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

if (argv[1].equals(“start-system-server”)) {
startSystemServer();
} else if (!argv[1].equals(“”)) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

Log.i(TAG, “Accepting command socket connections”);

if (ZYGOTE_FORK_MODE) {
runForkMode();
} else {
runSelectLoopMode();
}

closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, “Zygote died with exception”, ex);
closeServerSocket();
throw ex;
}
}

该函数重点完成如下3项工作:

1, registerZygoteSocket();
2, startSystemServer();—– > 核心方法,Zygote进程一分为二,此处分裂出一个system_server进程。
3, 至此system_server进程进入SystemServer.java—- >main()

先看下startSystemServer()方法:

private static boolean startSystemServer()
throws MethodAndArgsCaller, RuntimeException {
/* Hardcoded command line to start the system server */
String args[] = {
“–setuid=1000”,
“–setgid=1000”,
“–setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007”,
“–capabilities=130104352,130104352”,
“–runtime-init”,
“–nice-name=system_server”,
“com.android.server.SystemServer”,
};
ZygoteConnection.Arguments parsedArgs = null;

int pid;

try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}

/* For child process */
if (pid == 0) {
handleSystemServerProcess(parsedArgs);
}

return true;
}
com.android.server.SystemServer的创建,预示着SystemServer的的正式启动,自此Zygote一分为二。Zygote将系统服务交给SystemServer统一管理。而zygote则负责java运行时环境和Dalvik虚拟机的管理工作。
4.3,systemserver启动
system_server进程是android的第二大进程,其余zygote紧密联系,若其中任何一个进程死掉,就会导致系统死掉,其启动过程包含两个阶段Main()—– >init1()和init2()。
Init1(),为system_server的*阶段SystemServer.java— >Init1()的本地实现在com_android_server_SystemServer.cpp中。

先看frameworks\base\services\java\com\android\server\SystemServer.java的main()函数。

native public static void init1(String[] args);

public static void main(String[] args) {
if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
// If a device’s clock is before 1970 (before 0), a lot of
// APIs crash dealing with negative numbers, notably
// java.io.File#setLastModified, so instead we fake it and
// hope that time from cell towers or NTP fixes it
// shortly.
Slog.w(TAG, “System clock is before 1970; setting to 1970.”);
SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
}

if (SamplingProfilerIntegration.isEnabled()) {
SamplingProfilerIntegration.start();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SamplingProfilerIntegration.writeSnapshot(“system_server”, null);
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}

// Mmmmmm… more memory!
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();

// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);

System.loadLibrary(“android_servers”);
init1(args);
}

public static final void init2() {
Slog.i(TAG, “Entered the Android system server!”);
Thread thr = new ServerThread();
thr.setName(“android.server.ServerThread”);
thr.start();
}

其中init1()的本地实现在com_android_server_SystemServer.cpp中:

static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz)
{
system_init();
}

system_init()在frameworks\base\cmds\system_server\library\system_init.cpp中:

extern “C” status_t system_init()
{
ALOGI(“Entered system_init()”);

sp<ProcessState> proc(ProcessState::self());

sp<IServiceManager> sm = defaultServiceManager();
ALOGI(“ServiceManager: %p\n”, sm.get());

sp<GrimReaper> grim = new GrimReaper();
sm->asBinder()->linkToDeath(grim, grim.get(), 0);

char propBuf[PROPERTY_VALUE_MAX];
property_get(“system_init.startsurfaceflinger”, propBuf, “1”);
if (strcmp(propBuf, “1”) == 0) {
// Start the SurfaceFlinger
SurfaceFlinger::instantiate();
}

property_get(“system_init.startsensorservice”, propBuf, “1”);
if (strcmp(propBuf, “1”) == 0) {
// Start the sensor service
SensorService::instantiate();
}

// And now start the Android runtime. We have to do this bit
// of nastiness because the Android runtime initialization requires
// some of the core system services to already be started.
// All other servers should just start the Android runtime at
// the beginning of their processes’s main(), before calling
// the init function.
ALOGI(“System server: starting Android runtime.\n”);
AndroidRuntime* runtime = AndroidRuntime::getRuntime();

ALOGI(“System server: starting Android services.\n”);
JNIEnv* env = runtime->getJNIEnv();
if (env == NULL) {
return UNKNOWN_ERROR;
}
jclass clazz = env->FindClass(“com/android/server/SystemServer”);
if (clazz == NULL) {
return UNKNOWN_ERROR;
}
jmethodID methodId = env->GetStaticMethodID(clazz, “init2”, “()V”);
if (methodId == NULL) {
return UNKNOWN_ERROR;
}
env->CallStaticVoidMethod(clazz, methodId);

ALOGI(“System server: entering thread pool.\n”);
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI(“System server: exiting thread pool.\n”);

return NO_ERROR;
}
再回到SystemServer.java的main()中的init2():init2()将操作交给了内部类ServerThread处理,看起run()函数:

public void run() {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
SystemClock.uptimeMillis());

Looper.prepare();

android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND);

…….

AccountManagerService accountManager = null;
ContentService contentService = null;
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
VibratorService vibrator = null;
AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
WifiP2pService wifiP2p = null;
WifiService wifi = null;
NsdService serviceDiscovery= null;
IPackageManager pm = null;
Context context = null;
WindowManagerService wm = null;
BluetoothService bluetooth = null;
BluetoothA2dpService bluetoothA2dp = null;
DockObserver dock = null;
UsbService usb = null;
SerialService serial = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
InputManagerService inputManager = null;

//Bug#185069 fix low storage ,check the space&delete the temp file weather need.
DeviceStorageMonitorService.freeSpace();

…….
ServiceManager.addService(“xxx”,XXX);
…….

DevicePolicyManagerService devicePolicy = null;
StatusBarManagerService statusBar = null;
InputMethodManagerService imm = null;
AppWidgetService appWidget = null;
NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
TextServicesManagerService tsms = null;
LockSettingsService lockSettings = null;
DreamManagerService dreamy = null;

……

// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
final UsbService usbF = usb;
final ThrottleService throttleF = throttle;
final UiModeManagerService uiModeF = uiMode;
final AppWidgetService appWidgetF = appWidget;
final WallpaperManagerService wallpaperF = wallpaper;
final InputMethodManagerService immF = imm;
final RecognitionManagerService recognitionF = recognition;
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
final TextServicesManagerService textServiceManagerServiceF = tsms;
final StatusBarManagerService statusBarF = statusBar;
final DreamManagerService dreamyF = dreamy;
final InputManagerService inputManagerF = inputManager;
final BluetoothService bluetoothF = bluetooth;

ActivityManagerService.self().systemReady(new Runnable() {
public void run() {
Slog.i(TAG, “Making services ready”);

if (!headless) startSystemUi(contextF);
try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
reportWtf(“making Battery Service ready”, e);
}
try {
if (networkManagementF != null) networkManagementF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Managment Service ready”, e);
}
try {
if (networkStatsF != null) networkStatsF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Stats Service ready”, e);
}
try {
if (networkPolicyF != null) networkPolicyF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Policy Service ready”, e);
}
try {
if (connectivityF != null) connectivityF.systemReady();
} catch (Throwable e) {
reportWtf(“making Connectivity Service ready”, e);
}
try {
if (dockF != null) dockF.systemReady();
} catch (Throwable e) {
reportWtf(“making Dock Service ready”, e);
}
try {
if (usbF != null) usbF.systemReady();
} catch (Throwable e) {
reportWtf(“making USB Service ready”, e);
}
try {
if (uiModeF != null) uiModeF.systemReady();
} catch (Throwable e) {
reportWtf(“making UI Mode Service ready”, e);
}
try {
if (recognitionF != null) recognitionF.systemReady();
} catch (Throwable e) {
reportWtf(“making Recognition Service ready”, e);
}
Watchdog.getInstance().start();

// It is now okay to let the various system services start their
// third party code…

try {
if (appWidgetF != null) appWidgetF.systemReady(safeMode);
} catch (Throwable e) {
reportWtf(“making App Widget Service ready”, e);
}
try {
if (wallpaperF != null) wallpaperF.systemReady();
} catch (Throwable e) {
reportWtf(“making Wallpaper Service ready”, e);
}
try {
if (immF != null) immF.systemReady(statusBarF);
} catch (Throwable e) {
reportWtf(“making Input Method Service ready”, e);
}
try {
if (locationF != null) locationF.systemReady();
} catch (Throwable e) {
reportWtf(“making Location Service ready”, e);
}
try {
if (countryDetectorF != null) countryDetectorF.systemReady();
} catch (Throwable e) {
reportWtf(“making Country Detector Service ready”, e);
}
try {
if (throttleF != null) throttleF.systemReady();
} catch (Throwable e) {
reportWtf(“making Throttle Service ready”, e);
}
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
} catch (Throwable e) {
reportWtf(“making Network Time Service ready”, e);
}
try {
if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady();
} catch (Throwable e) {
reportWtf(“making Common time management service ready”, e);
}
try {
if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
} catch (Throwable e) {
reportWtf(“making Text Services Manager Service ready”, e);
}
try {
if (dreamyF != null) dreamyF.systemReady();
} catch (Throwable e) {
reportWtf(“making DreamManagerService ready”, e);
}
try {
if (inputManagerF != null) inputManagerF.systemReady(bluetoothF);
} catch (Throwable e) {
reportWtf(“making InputManagerService ready”, e);
}
}
});

//PowerManagerServer WakeLock dump thread
(new Thread(new WakelockMonitor(power))).start();

……

Looper.loop();
Slog.d(TAG, “System ServerThread is exiting!”);
}

该函数有3个重要功能:

1,ServiceManager.addService(“xxx”,XXX),将系统服务注册进去。

2,systemReady(),告诉已经实现该接口servers,系统已经启动OK。

3,WakelockMonitor的启动。

至此,systemserver的启动工作已经完成。

4.4,launcher启动
桌面launcher即Home:
1)源码:ActivityManagerService.java为入口,packages/apps/launcher*实现
2)说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,*终调用startHomeActivityLocked()启动launcher。Home在((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)。函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。这个函数的实现部分在文件:ActivityManagerService.java中。

先看ActivityManagerService.java的systemReady():

public void systemReady(final Runnable goingCallback) {

……

retrieveSettings();

if (goingCallback != null) goingCallback.run();

synchronized (this) {
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info
= (ApplicationInfo)apps.get(i);
if (info != null &&
!info.packageName.equals(“android”)) {
addAppLocked(info, false);
}
}
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
}

// Start up initial activity.
mBooting = true;

try {
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
Message msg = Message.obtain();
msg.what = SHOW_UID_ERROR_MSG;
mHandler.sendMessage(msg);
}
} catch (RemoteException e) {
}

mMainStack.resumeTopActivityLocked(null);
}
}
跳转至launcher的操作由resumeTopActivityLocked()完成,其实现在ActivityStack.java里的resumeTopActivityLocked()。

final ActivityManagerService mService;
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
// Find the first activity that is not finishing.
ActivityRecord next = topRunningActivityLocked(null);

// Remember how we’ll process this pause/resume situation, and ensure
// that the state is reset however we wind up proceeding.
final boolean userLeaving = mUserLeaving;
mUserLeaving = false;

if (next == null) {
// There are no more activities! Let’s just start up the
// Launcher…
if (mMainStack) {
ActivityOptions.abort(options);
return mService.startHomeActivityLocked(0);
}
}
从上述代码可以看出其实是走到了mService.startHomeActivityLocked(0),而这里的mService也就是ActivityManagerService.java,再次回到ActivityManagerService.java的startHomeActivityLocked(0),至此launcher启动完成。

4.5,lockscreen启动

源码:frameworks/policies/base/phone/com/android/internal/policy/impl/*lock*
说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,*终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置。此处不再详细分析。

frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java的systemReady()方法:

/** {@inheritDoc} */
public void systemReady() {
if (mKeyguardMediator != null) {
// tell the keyguard
mKeyguardMediator.onSystemReady();
}
synchronized (mLock) {
updateOrientationListenerLp();
mSystemReady = true;
mHandler.post(new Runnable() {
public void run() {
updateSettings();
}
});
}
}

*步,告诉锁屏控制器,系统已经启动完成,接下来有锁屏处理。 frameworks\base\policy\src\com\android\internal\policy\impl\KeyguardViewMediator.java:

public void onSystemReady() {
synchronized (this) {
if (DEBUG) Log.d(TAG, “onSystemReady”);
mSystemReady = true;
doKeyguardLocked();
}
}

再看其doKeyguardLocked()方法:

private void doKeyguardLocked() {

if(engModeFlag){
Log.d(TAG, “show engmode!”);
engModeFlag = false;
return ;
}

// if another app is disabling us, don’t show
if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because externally disabled”);

// note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
// for an occasional ugly flicker in this situation:
// 1) receive a call with the screen on (no keyguard) or make a call
// 2) screen times out
// 3) user hits key to turn screen back on
// instead, we reenable the keyguard when we know the screen is off and the call
// ends (see the broadcast receiver below)
// TODO: clean this up when we have better support at the window manager level
// for apps that wish to be on top of the keyguard
return;
}

// if the keyguard is already showing, don’t bother
if (mKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because it is already showing”);
return;
}

final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
final boolean lockedOrMissing = isSimLockedOrMissing();

if (!lockedOrMissing && !provisioned) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because device isn’t provisioned”
+ ” and the sim is not locked or missing”);
return;
}

if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) {
if (DEBUG) Log.d(TAG, “doKeyguard: not showing because lockscreen is off”);
return;
}

if (DEBUG) Log.d(TAG, “doKeyguard: showing the lock screen”);
showLocked();
}
至此,锁屏启动完成。
4.6,othersapps启动
系统启动完成后,launcher会加载系统已经安装的apk,并显示在launcher上。

至此,android启动完成。

5,android启动动画效果剖析
在android启动的过程中我们通常可以看到若干个启动画面,均代表着不同的启动阶段,接下来根据启动阶段分析启动画面。

uboot启动:会有一帧 uboot logo。

kernel启动:会有一帧kernel logo。(默认不显示,其控制宏是默认关闭的)

android启动:会有一帧静态图片+一个闪动的图片序列(即开机动画)。

通常情况下,我们在分析android的开机动画效果时,很少去分析uboot logo和kernel logo,因为ubootlogo 属于uboot阶段,kernel logo 属于linux范围。正常情况下,我们在down版本,烧到手机里去时,会吧logo.bmp加进去,这是系统的处理是:uboot logo,kernel logo,android static logo是同一张图片,即我们加的logo.bmp。

双framebuffer显示logo机制分析:本来一直走的是一级logo显示,从uboot logo一直持续到系统动画,但考虑期间时间偏长,欲采用标准三级logo。1、uboot logo  2、kernle logo 3 initlogo.rle *后动画bootanimation.zip。但是kernel 对framebuffer修改较大,故考虑在uboot开始和结束显示两张logo(第二幅logo显示调用在theKernel()跳入内核函数之前),kernel跳过。uboot 直接刷屏显示第二幅logo 动作过慢,效果不佳,经考虑采用双buffer策略。思路:

1.原来只要显示一张uboot logo :把nand 中boot.logo 拷贝至lcd_base+fbsize处,然后搬至lcd_base显示;
2.现在创建第二个framebuffer于lcd_base+2*fbsize处,在显示第二幅logo前把nand 中第二幅logo 仍然拷贝至lcd_base+fbsize处,然后搬至lcd_base+2*fbsize第二个framebuffer基地址;
3.把第二个framebuffer基地址告诉lcd 控制寄存器,更新framebuffer基地址;
4.但在kernel中,寄存器仍然会指向*个framebuffer基地址,那么第二幅logo显示犹如昙花一现啊,不过这个问题好解决,既然第二幅logo已经搬进了第二个framebuffer那,那么只要在进入内核前做一个memcpy就好了。
注:logo是bmp格式,在拷贝前需要进行相应的解析,参考uboot给的解析代码,自定义函数。
5.1,uboot logo
以正常模式启动分析uboot logo。即normal_mode.c根据前部分的分析可知,流程会走至normal_nand_mode.c的vlx_nand_boot()函数。

//读取下载到nand中的boot_logo,就是开机亮的那一屏
off=part->offset;
nand = &nand_info[dev->id->num];
//read boot image header
size = 1<<19;//where the size come from????//和dowload工具中的地址一致
char * bmp_img = malloc(size);
if(!bmp_img){
printf(“not enough memory for splash image\n”);
return;
}
ret = nand_read_offset_ret(nand, off, &size, (void *)bmp_img, &off);
if(ret != 0){
printf(“function: %s nand read error %d\n”, __FUNCTION__, ret);
return;
}
//*次LCD logo
lcd_display_logo(backlight_set,(ulong)bmp_img,size);
即由lcd_display_logo()完成相关操作。该函数在normal_mode.c中定义。
void lcd_display_logo(int backlight_set,ulong bmp_img,size_t size)
{
#ifdef CONFIG_SPLASH_SCREEN
extern int lcd_display_bitmap(ulong bmp_image, int x, int y);
extern void lcd_display(void);
extern void *lcd_base;
extern void Dcache_CleanRegion(unsigned int addr, unsigned int length);
extern void set_backlight(uint32_t value);
if(backlight_set == BACKLIGHT_ON){
lcd_display_bitmap((ulong)bmp_img, 0, 0);
#if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830)
Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large.
#endif
lcd_display();
set_backlight(255);
}else{
memset((unsigned int)lcd_base, 0, size);
#if defined(CONFIG_SC8810) || defined(CONFIG_SC8825) || defined(CONFIG_SC8830)
Dcache_CleanRegion((unsigned int)(lcd_base), size);//Size is to large.
#endif
lcd_display();
}
#endif
}
5.2,kernel logo
kernel logo 属于linux系统自带的logo机制,由于在android平台其显示默认是关闭的,此处不做多的分析,详细可参考博文:Android系统的开机画面显示过程分析 ,该博文只分析了启动过程的 kernel logo,android logo anim。

相关代码:
/kernel/drivers/video/fbmem.c
/kernel/drivers/video/logo/logo.c
/kernel/drivers/video/logo/Kconfig
/kernel/include/linux/linux_logo.h

static int nologo;
module_param(nologo, bool, 0);
MODULE_PARM_DESC(nologo, “Disables startup logo”);
/* logo’s are marked __initdata. Use __init_refok to tell
* modpost that it is intended that this function uses data
* marked __initdata.
*/
const struct linux_logo * __init_refok fb_find_logo(int depth)
{
const struct linux_logo *logo = NULL;
if (nologo)
return NULL;
……
}
5.3,android logo anim
Android 系统启动后,init.c中main()调用queue_builtin_action(console_init_action, “console_init”)时会根据console_init_action函数调用load_565rle_image()函数读取/initlogo.rle(一张565 rle压缩的位图),如果读取成功,则在/dev/graphics/fb0显示Logo图片;如果读取失败,则将/dev/tty0设为TEXT模式, 并打开/dev/tty0,输出文本“A N D R I O D”字样。

 

static int console_init_action(int nargs, char **args)
{
int fd;
char tmp[PROP_VALUE_MAX];

if (console[0]) {
snprintf(tmp, sizeof(tmp), “/dev/%s”, console);
console_name = strdup(tmp);
}

fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);

if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open(“/dev/tty0”, O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = “\n”
“\n”
“\n”
“\n”
“\n”
“\n”
“\n” // console is 40 cols x 30 lines
“\n”
“\n”
“\n”
“\n”
“\n”
“\n”
“\n”
” A N D R O I D “;
write(fd, msg, strlen(msg));
close(fd);
}
}
return 0;
}

由此调用logo.c 的load_565rle_image()函数。

int load_565rle_image(char *fn)
{
struct FB fb;
struct stat s;
unsigned short *data, *bits, *ptr;
unsigned count, max;
int fd;

if (vt_set_mode(1))
return -1;

fd = open(fn, O_RDONLY);
if (fd < 0) {
ERROR(“cannot open ‘%s’\n”, fn);
goto fail_restore_text;
}

if (fstat(fd, &s) < 0) {
goto fail_close_file;
}

data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
goto fail_close_file;

if (fb_open(&fb))
goto fail_unmap_data;

max = fb_width(&fb) * fb_height(&fb);
ptr = data;
count = s.st_size;
bits = fb.bits;
while (count > 3) {
unsigned n = ptr[0];
if (n > max)
break;
android_memset16(bits, ptr[1], n << 1);
bits += n;
max -= n;
ptr += 2;
count -= 4;
}

munmap(data, s.st_size);
fb_update(&fb);
fb_close(&fb);
close(fd);
unlink(fn);
return 0;

fail_unmap_data:
munmap(data, s.st_size);
fail_close_file:
close(fd);
fail_restore_text:
vt_set_mode(0);
return -1;
}

该图片格式是565RLE image format格式的,可用工具将bmp格式转化为rle格式。之后会有init.rc 并发开机动画。

相关文件:
/frameworks/base/cmds/bootanimation/BootAnimation.h
/frameworks/base/cmds/bootanimation/BootAnimation.cpp
/frameworks/base/cmds/bootanimation/bootanimation_main.cpp
/system/core/init/init.c
/system/core/rootdir/init.rc

init.c解析init.rc(其中定义服务:“service bootanim /system/bin/bootanimation”),bootanim 服务由SurfaceFlinger.readyToRun()(property_set(“ctl.start”, “bootanim”);)执行开机动画、bootFinished()(property_set(“ctl.stop”, “bootanim”);)执行停止开机动画。 BootAnimation.h和BootAnimation.cpp文件放到了/frameworks/base/cmds /bootanimation目录下了,增加了一个入口文件bootanimation_main.cpp。Android.mk文件中可以看到,将开机 动画从原来的SurfaceFlinger里提取出来了,生成可执行文件:bootanimation。Android.mk代码如下:

//=============Android.mk======================
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
bootanimation_main.cpp \
BootAnimation.cpp
# need “-lrt” on Linux simulator to pick up clock_gettime
ifeq ($(TARGET_SIMULATOR),true)
ifeq ($(HOST_OS),linux)
LOCAL_LDLIBS += -lrt
endif
endif
LOCAL_SHARED_LIBRARIES := \
libcutils \
libutils \
libui \
libcorecg \
libsgl \
libEGL \
libGLESv1_CM \
libmedia
LOCAL_C_INCLUDES := \
$(call include-path-for, corecg graphics)
LOCAL_MODULE:= bootanimation
include $(BUILD_EXECUTABLE)
//==========================================
备注:
1,adb shell后,可以直接运行“bootanimation”来重新看开机动画,它会一直处于动画状态,而不会停止。

2,adb shell后,命令“setprop ctl.start bootanim”执行开机动画;命令“getprop ctl.start bootanim”停止开机动画。这两句命令分别对应SurfaceFlinger.cpp的两句语 句:property_set(“ctl.start”, “bootanim”);和property_set(“ctl.stop”, “bootanim”)。

至此android启动动画分析结束。

Android系统启动过程学习

使用 android 手机已经长时间了,同时,从大学学习 android 开发开始,也进行过多款 android app 项目的开发,但是对 android 内部的启动过程,即当我们从按下电源键开机开始, android 系统内部是如何运行的,由于android 系统的内核使用的是 linux 内核,那么在启动过程中,android 系统和桌面Linux系统的启动过程是否是一样的?我们在之前的一篇博客中,曾学习过Linux内核的启动过程,在这里,我们学习一下android系统的启动过程,并从大体代码上讲解其启动过程。

启动步骤
在android系统的启动过程中,大体可以分为以下几个步骤:

%title插图%num

android启动过程分析
*步 启动电源以及启动系统
当电源按下的时候,会引导固化在芯片中的代码从预定义的位置开始启动,并加载引导程序到内存,即RAM中,然后执行。

第二步 引导程序运行
引导程序,英文名称为Boot Loader,顾名思义,其为启动加载器,其主要作用是引导android系统内核的启动。引导程序是运行的*个程序。其主要作用是在引导内核启动之前,检测相关硬件以及外部的RAM,设置网络,内存等,并根据相关参数或输入数据设置内核。
Boot Loader引导程序可以在\bootable\bootloader\legacy\usbloader中找到,一般的加载器包含着两个重要的文件:

init.S初始化堆栈,清空BBS栈,调用main.c的_main()函数
main.c初始化硬件(闹钟,主板,键盘,控制台),创建Linux标签
第三步 内核运行
当引导程序引导内核启动之后,android的内核运行和linux的内核运行相似。内核启动的时候,设置缓存,被保护的存储器,计划列表,加载驱动。当内核完成系统设置之后,便找到系统文件中”init”文件,然后启动系统的*个进程。

第四步 init运行
init进程是android系统运行的*个进程,也就是说所有的android进程都是直接或间接的被init进程创建的。在init进程运行过程中,主要负责两个事情,一个是挂载系统目录,像/dev,/proc,/sys等,另外一件事情是执行init.rc文件。

init位于/system/core/init目录中。
init.rc位于/system/core/rootdir目录中。
在/system/core/init/readme.txt文件中可以学习.rc文件的相关语法,我们将在下面的博客中学习该语法。
总体来说,在init.rc文件中,主要是启动相关进程和服务,同时设置相关参数和挂载相关目录。

我们先来看一下init中的相关代码:

int main(int argc, char **argv)
{
//…..
#ifndef NO_DEVFS_SETUP
mkdir(“/dev”, 0755);
mkdir(“/proc”, 0755);
mkdir(“/sys”, 0755);

mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
mkdir(“/dev/pts”, 0755);
mkdir(“/dev/socket”, 0755);
mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
mount(“proc”, “/proc”, “proc”, 0, NULL);
mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);

close(open(“/dev/.booting”, O_WRONLY | O_CREAT, 0000));

open_devnull_stdio();
klog_init();
#endif
property_init();

//…….

is_charger = !strcmp(bootmode, “charger”);

INFO(“property init\n”);
if (!is_charger)
property_load_boot_defaults();

INFO(“reading config file\n”);

if (!charging_mode_booting())
init_parse_config_file(“/init.rc”);
else
init_parse_config_file(“/lpm.rc”);

/* Check for an emmc initialisation file and read if present */
if (emmc_boot && access(“/init.emmc.rc”, R_OK) == 0) {
INFO(“Reading emmc config file”);
init_parse_config_file(“/init.emmc.rc”);
}

/* Check for a target specific initialisation file and read if present */
if (access(“/init.target.rc”, R_OK) == 0) {
INFO(“Reading target specific config file”);
init_parse_config_file(“/init.target.rc”);
}

//…….

在这些事情处理完成之后,在init.rc中便会启动zygote进程。

service zygote /system/bin/app_process -Xzygote /system/bin –zygote –start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd

第五步 zygote进程
在java中不同的虚拟机实例会为不同的应用分配不同的内存。假如android应用应该尽可能快的启动,但如果android系统为每一个应用启动不同的Dalvik虚拟机实例,就会消耗大量的内存以及时间。因此为了解决这个问题,Android系统创造了Zygote。Zygote让Dalvik虚拟机共享代码,低内存占用以及*小的启动时间成为可能。Zygote是一个虚拟机进程,正如我们在前一个步骤所说的在系统引导的时候启动。Zygote预加载以及初始化核心库类。

Zygote加载进程:

public static void main(String argv[]) {
try {
// Start profiling the zygote initialization.
SamplingProfilerIntegration.start();

registerZygoteSocket();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());

preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());

// Finish profiling the zygote initialization.
SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup
gcAndFinalize();

// Disable tracing so that forked processes do not inherit stale tracing tags from
// Zygote.
Trace.setTracingEnabled(false);

// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

if (argv[1].equals(“start-system-server”)) {
startSystemServer(); //启动系统服务
} else if (!argv[1].equals(“”)) {
throw new RuntimeException(argv[0] + USAGE_STRING);
}

Log.i(TAG, “Accepting command socket connections”);

runSelectLoop();

closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, “Zygote died with exception”, ex);
closeServerSocket();
throw ex;
}
}

加载ZygoteInit类,代码位于/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中。
registerZygoteSocket()为zygote命令连接注册一个服务器套接字。
preload()预加载类,资源文件以及OpenGL。
第六步 系统服务或服务
在Zygote完成相应的初始化之后,开始申请启动系统服务,系统服务是android系统中供上层运行的基础。系统服务同时使用native以及java编写,系统服务可以认为是一个进程。同一个系统服务在Android SDK可以让System Services形式获得。

核心服务:

启动电源管理器;
创建Activity管理器;
启动电话注册;
启动包管理器;
设置Activity管理服务为系统进程;
启动上下文管理器;
启动系统Context Providers;
启动电池服务;
启动定时管理器;
启动传感服务;
启动窗口管理器;
启动蓝牙服务;
启动挂载服务。
其他服务:

启动状态栏服务;
启动硬件服务;
启动网络状态服务;
启动网络连接服务;
启动通知管理器;
启动设备存储监视服务;
启动定位管理器;
启动搜索服务;
启动剪切板服务;
启动登记服务;
启动壁纸服务;
启动音频服务;
启动耳机监听;
启动AdbSettingsObserver(处理adb命令)。

第七步 引导完成
一单系统服务启动,android系统的引导过程就完成了。此时,”ACTION_BOOT_COMPLETE”开机启动广播就发出去了。

Android系统五大布局详解Layout

我们知道Android系统应用程序一般是由多个Activity组成,而这些Activity以视图的形式展现在我们面前,视图都是由一个一个的组件构成的。组件就是我们常见的Button、TextEdit等等。那么我们平时看到的Android手机中那些漂亮的界面是怎么显示出来的呢?这就要用到Android的布局管理器了,网上有人比喻的很好:布局好比是建筑里的框架,组件按照布局的要求依次排列,就组成了用于看见的漂亮界面了。

在分析布局之前,我们首先看看控件:Android中任何可视化的控件都是从android.veiw.View继承而来的,系统提供了两种方法来设置视图:*种也是我们*常用的的使用XML文件来配置View的相关属性,然后在程序启动时系统根据配置文件来创建相应的View视图。第二种是我们在代码中直接使用相应的类来创建视图。

如何使用XML文件定义视图:

每个Android项目的源码目录下都有个res/layout目录,这个目录就是用来存放布局文件的。布局文件一般以对应activity的名字命名,以 .xml 为后缀。在xml中为创建组件时,需要为组件指定id,如:android:id=”@+id/名字”系统会自动在gen目录下创建相应的R资源类变量。

如何在代码中使用视图:

在代码中创建每个Activity时,一般是在onCreate()方法中,调用setContentView()来加载指定的xml布局文件,然后就可以通过findViewById()来获得在布局文件中创建的相应id的控件了,如Button等。

如:

[html]  view plain copy

  1. private Button btnSndMag;
  2. public void onCreate(Bundle savedInstanceState) {
  3.     super.onCreate(savedInstanceState);
  4.     setContentView(R.layout.main);  // 加载main.xml布局文件
  5.     btnSndMag = (Button)this.findViewById(R.id.btnSndMag); // 通过id找到对于的Button组件
  6.     ….
  7. }

 

下面我们来介绍Android系统中为我们提供的五大布局:LinearLayout(线性布局)、FrameLayout(单帧布局)、AbsoluteLayout(*对布局)、TablelLayout(表格布局)、RelativeLayout(相对布局)。其中*常用的的是LinearLayout、TablelLayout和RelativeLayout。这些布局都可以嵌套使用。

(1)LinearLayout 线性布局

线性布局是按照水平或垂直的顺序将子元素(可以是控件或布局)依次按照顺序排列,每一个元素都位于前面一个元素之后。线性布局分为两种:水平方向和垂直方向的布局。分别通过属性android:orientation=”vertical” 和 android:orientation=”horizontal”来设置。

android:layout_weight 表示子元素占据的空间大小的比例,有人说这个值大小和占据空间成正比,有人说反比。我在实际应用中设置和网上资料显示的刚好相反,这个问题后面会专门写一篇文章来分析。现在我们只需要按照正比例来设置就可以。

例如下面我们实现一个如图所示的简易计算器界面:

%title插图%num

代码:

[html]  view plain copy

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  2.     xmlns:tools=“http://schemas.android.com/tools”
  3.     android:orientation=“vertical”
  4.     android:layout_width=“match_parent”
  5.     android:layout_height=“match_parent”
  6.     android:background=“#FFFFFF”
  7.     tools:context=“.MainActivity” >
  8.     // 这里*行显示标签为一个水平布局
  9.     <LinearLayout
  10.         android:layout_width=“match_parent”
  11.         android:layout_height=“wrap_content”
  12.         android:orientation=“horizontal” >
  13.         <EditText
  14.             android:id=“@+id/msg”
  15.             android:inputType=“number”
  16.             android:layout_width=“match_parent”
  17.             android:layout_height=“wrap_content”
  18.             android:text=“”>
  19.         </EditText>
  20.     </LinearLayout>
  21.     // 第二行为 mc m+ m- mr 四个Button构成一个水平布局
  22.     <LinearLayout
  23.         android:layout_width=“match_parent”
  24.         android:layout_height=“wrap_content”
  25.         android:orientation=“horizontal” >
  26.         <Button
  27.             android:layout_width=“match_parent”
  28.             android:layout_height=“wrap_content”
  29.             android:text=“mc” android:layout_weight=“1”>
  30.         </Button>
  31.         <Button
  32.             android:layout_width=“match_parent”
  33.             android:layout_height=“wrap_content”
  34.             android:text=“m+” android:layout_weight=“1”>
  35.         </Button>
  36.         <Button
  37.             android:layout_width=“match_parent”
  38.             android:layout_height=“wrap_content”
  39.             android:text=“m-“ android:layout_weight=“1”>
  40.         </Button>
  41.         <Button
  42.             android:layout_width=“match_parent”
  43.             android:layout_height=“wrap_content”
  44.             android:text=“mr” android:layout_weight=“1”>
  45.         </Button>
  46.     </LinearLayout>
  47.     // 同上 C +/-  / * 四个Button构成一个水平布局
  48.       <LinearLayout
  49.           android:layout_width=“match_parent”
  50.           android:layout_height=“wrap_content”
  51.           android:orientation=“horizontal” >
  52.           <Button
  53.               android:layout_width=“match_parent”
  54.               android:layout_height=“wrap_content”
  55.               android:layout_weight=“1”
  56.               android:text=“C” >
  57.           </Button>
  58.           <Button
  59.               android:layout_width=“match_parent”
  60.               android:layout_height=“wrap_content”
  61.               android:layout_weight=“1”
  62.               android:text=“+/-“ >
  63.           </Button>
  64.           <Button
  65.               android:layout_width=“match_parent”
  66.               android:layout_height=“wrap_content”
  67.               android:layout_weight=“1”
  68.               android:text=“/” >
  69.           </Button>
  70.           <Button
  71.               android:layout_width=“match_parent”
  72.               android:layout_height=“wrap_content”
  73.               android:layout_weight=“1”
  74.               android:text=“*” >
  75.           </Button>
  76.       </LinearLayout>
  77.       <LinearLayout
  78.         android:layout_width=“match_parent”
  79.         android:layout_height=“wrap_content”
  80.         android:orientation=“horizontal” >
  81.         <Button
  82.             android:layout_width=“match_parent”
  83.             android:layout_height=“wrap_content”
  84.             android:text=“7” android:layout_weight=“1”>
  85.         </Button>
  86.         <Button
  87.             android:layout_width=“match_parent”
  88.             android:layout_height=“wrap_content”
  89.             android:text=“8” android:layout_weight=“1”>
  90.         </Button>
  91.         <Button
  92.             android:layout_width=“match_parent”
  93.             android:layout_height=“wrap_content”
  94.             android:text=“9” android:layout_weight=“1”>
  95.         </Button>
  96.         <Button
  97.             android:layout_width=“match_parent”
  98.             android:layout_height=“wrap_content”
  99.             android:text=“-“ android:layout_weight=“1”>
  100.         </Button>
  101.     </LinearLayout>
  102.     <LinearLayout
  103.         android:layout_width=“match_parent”
  104.         android:layout_height=“wrap_content”
  105.         android:orientation=“horizontal” >
  106.         <Button
  107.             android:layout_width=“match_parent”
  108.             android:layout_height=“wrap_content”
  109.             android:layout_weight=“1”
  110.             android:text=“4” >
  111.         </Button>
  112.         <Button
  113.             android:layout_width=“match_parent”
  114.             android:layout_height=“wrap_content”
  115.             android:layout_weight=“1”
  116.             android:text=“5” >
  117.         </Button>
  118.         <Button
  119.             android:layout_width=“match_parent”
  120.             android:layout_height=“wrap_content”
  121.             android:layout_weight=“1”
  122.             android:text=“6” >
  123.         </Button>
  124.         <Button
  125.             android:layout_width=“match_parent”
  126.             android:layout_height=“wrap_content”
  127.             android:layout_weight=“1”
  128.             android:text=“+” >
  129.         </Button>
  130.     </LinearLayout>
  131.     // *外层是一个水平布局,由左边上面一行1 2 3三个Button,下面一行的0 . 两个Button 和 右边的=构成
  132.      <LinearLayout android:orientation=“horizontal”
  133.         android:layout_width=“match_parent”
  134.         android:layout_height=“wrap_content”>
  135.         // 这里 1 2 3 和 下面的 0 . 构成一个垂直布局
  136.         <LinearLayout android:orientation=“vertical”
  137.             android:layout_weight=“3”
  138.             android:layout_width=“wrap_content”
  139.             android:layout_height=“wrap_content”>
  140.             // 这里的 1 2 3 构成一个水平布局
  141.             <LinearLayout android:orientation=“horizontal”
  142.                 android:layout_width=“match_parent”
  143.                 android:layout_height=“wrap_content”>
  144.                 <Button
  145.                     android:layout_width=“wrap_content”
  146.                     android:layout_height=“wrap_content”
  147.                     android:layout_weight=“1”
  148.                     android:text=“1”></Button>
  149.                 <Button
  150.                     android:layout_width=“wrap_content”
  151.                     android:layout_height=“wrap_content”
  152.                     android:layout_weight=“1”
  153.                     android:text=“2”></Button>
  154.                 <Button
  155.                     android:layout_width=“wrap_content”
  156.                     android:layout_height=“wrap_content”
  157.                     android:layout_weight=“1”
  158.                     android:text=“3”></Button>
  159.             </LinearLayout>
  160.             // 这里的 0 和 . 构成一个水平布局,注意这里的android_weight参数设置
  161.             <LinearLayout android:orientation=“horizontal”
  162.                 android:layout_width=“match_parent”
  163.                 android:layout_height=“wrap_content”>
  164.                 <Button
  165.                     android:layout_width=“0px”
  166.                     android:layout_height=“wrap_content”
  167.                     android:layout_weight=“2”
  168.                     android:text=“0”></Button>
  169.                 <Button
  170.                     android:layout_width=“0px”
  171.                     android:layout_height=“wrap_content”
  172.                     android:layout_weight=“1”
  173.                     android:text=“.”></Button>
  174.             </LinearLayout>
  175.         </LinearLayout>
  176.         // 这里一个单独Button构成的垂直布局
  177.         <LinearLayout android:orientation=“vertical”
  178.             android:layout_weight=“1”
  179.             android:layout_width=“wrap_content”
  180.             android:layout_height=“match_parent”>
  181.             <Button
  182.                 android:layout_width=“match_parent”
  183.                 android:layout_height=“match_parent”
  184.                 android:text=“=”></Button>
  185.         </LinearLayout>
  186.      </LinearLayout>
  187. </LinearLayout>

(2)TableLayout 表格布局

表格布局,适用于多行多列的布局格式,每个TableLayout是由多个TableRow组成,一个TableRow就表示TableLayout中的每一行,这一行可以由多个子元素组成。实际上TableLayout和TableRow都是LineLayout线性布局的子类。但是TableRow的参数android:orientation属性值固定为horizontal,且android:layout_width=MATCH_PARENT,android:layout_height=WRAP_CONTENT。所以TableRow实际是一个横向的线性布局,且所以子元素宽度和高度一致。

注意:在TableLayout中,单元格可以为空,但是不能跨列,意思是只能不能有相邻的单元格为空。

在TableLayout布局中,一列的宽度由该列中*宽的那个单元格指定,而该表格的宽度由父容器指定。可以为每一列设置以下属性:

Shrinkable  表示该列的宽度可以进行收缩,以使表格能够适应父容器的大小

Stretchable 表示该列的宽度可以进行拉伸,以使能够填满表格中的空闲空间

Collapsed  表示该列会被隐藏

TableLayout中的特有属性:

android:collapseColumns

android:shrinkColumns

android:stretchColumns = “0,1,2,3”// 表示产生4个可拉伸的列

Demo:我们想设计一个如下所以的一个三行三列的表格,但是第二行我们只想显示2个表格:

%title插图%num

[java]  view plain copy

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <TableLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:orientation=“vertical”
  4.     android:shrinkColumns=“0,1,2”  // 设置三列都可以收缩  
  5.     android:stretchColumns=“0,1,2” // 设置三列都可以拉伸 如果不设置这个,那个显示的表格将不能填慢整个屏幕
  6.     android:layout_width=“fill_parent”
  7.     android:layout_height=“fill_parent” >
  8.     <TableRow android:layout_width=“fill_parent”
  9.         android:layout_height=“wrap_content”>
  10.         <Button android:gravity=“center”
  11.             android:padding=“10dp”
  12.             android:text=“Button1”>
  13.         </Button>
  14.         <Button android:gravity=“center”
  15.             android:padding=“10dp”
  16.             android:text=“Button2”>
  17.         </Button>
  18.         <Button android:gravity=“center”
  19.             android:padding=“10dp”
  20.             android:text=“Button3”>
  21.         </Button>
  22.     </TableRow>
  23.     <TableRow android:layout_width=“fill_parent”
  24.         android:layout_height=“wrap_content”>
  25.         <Button android:gravity=“center”
  26.             android:padding=“10dp”
  27.             android:text=“Button4”>
  28.         </Button>
  29.         <Button android:gravity=“center”
  30.             android:padding=“10dp”
  31.             android:text=“Button5”>
  32.         </Button>
  33.     </TableRow>
  34.     <TableRow android:layout_width=“fill_parent”
  35.         android:layout_height=“wrap_content”>
  36.         <Button android:gravity=“center”
  37.             android:padding=“10dp”
  38.             android:text=“Button6”>
  39.         </Button>
  40.         <Button android:gravity=“center”
  41.             android:padding=“10dp”
  42.             android:text=“Button7”>
  43.         </Button>
  44.         <Button android:gravity=“center”
  45.             android:padding=“10dp”
  46.             android:text=“Button8”>
  47.         </Button>
  48.     </TableRow>
  49. </TableLayout>

 

(3)RelativeLayout 相对布局

RelativeLayout继承于android.widget.ViewGroup,其按照子元素之间的位置关系完成布局的,作为Android系统五大布局中*灵活也是*常用的一种布局方式,非常适合于一些比较复杂的界面设计。

注意:在引用其他子元素之前,引用的ID必须已经存在,否则将出现异常。

常用的位置属性:

 

[java]  view plain copy

  1. android:layout_toLeftOf         该组件位于引用组件的左方
  2. android:layout_toRightOf        该组件位于引用组件的右方
  3. android:layout_above            该组件位于引用组件的上方
  4. android:layout_below                该组件位于引用组件的下方
  5. android:layout_alignParentLeft      该组件是否对齐父组件的左端
  6. android:layout_alignParentRight     该组件是否齐其父组件的右端
  7. android:layout_alignParentTop       该组件是否对齐父组件的顶部
  8. android:layout_alignParentBottom    该组件是否对齐父组件的底部
  9. android:layout_centerInParent       该组件是否相对于父组件居中
  10. android:layout_centerHorizontal     该组件是否横向居中
  11. android:layout_centerVertical       该组件是否垂直居中

 

Demo:利用相对布局设计一个如下图所示的界面:

%title插图%num

源码:

[html]  view plain copy

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     android:layout_width=“fill_parent”
  4.     android:layout_height=“fill_parent” >
  5.     <Button android:id=“@+id/btn1”
  6.         android:layout_width=“wrap_content”
  7.         android:layout_height=“wrap_content”
  8.         android:layout_centerInParent=“true”
  9.         android:layout_centerHorizontal=“true”
  10.         android:text=“Button1”
  11.         ></Button>
  12.     <Button android:id=“@+id/btn2”
  13.         android:layout_width=“wrap_content”
  14.         android:layout_height=“wrap_content”
  15.         android:layout_toLeftOf=“@id/btn1”
  16.         android:layout_above=“@id/btn1”
  17.         android:text=“Button2”
  18.         ></Button>
  19.     <Button android:id=“@+id/btn3”
  20.         android:layout_width=“wrap_content”
  21.         android:layout_height=“wrap_content”
  22.         android:layout_toRightOf=“@id/btn1”
  23.         android:layout_above=“@id/btn1”
  24.         android:text=“Button3”
  25.         ></Button>
  26.     <Button android:id=“@+id/btn4”
  27.         android:layout_width=“wrap_content”
  28.         android:layout_height=“wrap_content”
  29.         android:layout_toRightOf=“@id/btn2”
  30.         android:layout_toLeftOf=“@id/btn3”
  31.         android:layout_above=“@id/btn2”
  32.         android:text=“Button4”
  33.         ></Button>
  34. </RelativeLayout>

(4)FrameLayout 框架布局

将所有的子元素放在整个界面的左上角,后面的子元素直接覆盖前面的子元素,所以用的比较少。

(5) AbsoluteLayou *对布局

*对布局中将所有的子元素通过设置android:layout_x 和 android:layout_y属性,将子元素的坐标位置固定下来,即坐标(android:layout_x, android:layout_y) ,layout_x用来表示横坐标,layout_y用来表示纵坐标。屏幕左上角为坐标(0,0),横向往右为正方,纵向往下为正方。实际应用中,这种布局用的比较少,因为Android终端一般机型比较多,各自的屏幕大小。分辨率等可能都不一样,如果用*对布局,可能导致在有的终端上显示不全等。

除上面讲过之外常用的几个布局的属性:
(1)layout_margin 
用于设置控件边缘相对于父控件的边距
android:layout_marginLeft
android:layout_marginRight
android:layout_marginTop
android:layout_marginBottom

(2) layout_padding 
用于设置控件内容相对于控件边缘的边距
android:layout_paddingLeft
android:layout_paddingRight
android:layout_paddingTop
android:layout_paddingBottom

(3) layout_width/height
用于设置控件的高度和宽度
wrap_content 内容包裹,表示这个控件的里面文字大小填充
fill_parent 跟随父窗口
match_parent

(4) gravity 
用于设置View组件里面内容的对齐方式
top bottom   left   right   center等

(5) android:layout_gravity  
用于设置Container组件的对齐方式
android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐

2020 Google 开发者大会主题演讲,都在这里

 

%title插图%num

今年大会上,谷歌为开发者带来了一大波开发技术和工具更新,助力更高效、更轻松的开发体验。

Android 11:以人为本,控制和隐私

在 Android 11 中,我们专注于三个关键主题:以人为本,控制和隐私,并将这些更新带到众多 Android 设备上!

Android团队也一直在努力改善移动开发者的体验。Android Studio 4.1 和 4.2 Canary 更新,提高开发效率。Kotlin 和 Jetpack 库的完善,可以帮助开发者快速构建应用。

%title插图%num

TensorFlow 2.4:解决各行各业的实际问题

TensorFlow 带来了 2.4 版本更新,介绍了在汽车、商业、游戏等行业的应用。从研究人员,到数据科学家、工程师、开发人员,TensorFlow 都有相应的方案,帮助高效解决问题

%title插图%num

Flutter:增强开放,拓展开发场景

作为全球增长速度第二的开源项目,越来越多国内开发者使用 Flutter 实现跨平台开发,包括腾讯英语君团队、阿里闲鱼团队等等。其在开放性上的进步,得益于开源社区、生态建设、对 Web 的支持。

%title插图%num

Web:多方面增强用户体验

团队以 Private Sandbox 提升用户隐私保护,加入 Core Web Vitals 核心网页指标,增强用户体验。PWA 与 Android 集成、Google Play 商店连接,Chrome Dev Tools 的更新,为开发者带来更多用户,也助其更轻松开发。

%title插图%num

Firebase :提高应用质量

Firebase 整合谷歌各产品和云服务,让开发者在单个平台上轻松构建移动端、 Web 端的应用。Firebase 模拟套件和性能监测工具,则提高应用质量,让开发更快更简单。

%title插图%num

Wear OS :发展生态,提高生产力

Wear OS 今年从开机速度、配对速度、续航能力多个方面实现提升,从硬件和软件层面发展合作伙伴生态,帮助人们获得实时信息,提高生产力

%title插图%num

ARCore:打造有趣且真实的应用

ARCore 已被应用在 7 亿台设备和成千上万款 APP 上,包括美图、滴滴、有道少儿词典等。瞬间放置 API 、深度 API 以及持久云锚点能帮助开发者打造有趣、真实的应用,并使用在更丰富的场景中。

%title插图%num

Google Assistant:搭建全面的智能家居生态

Google Assistant 发布了兼容拓展的娱乐智能家居(SHED),用户用 Google Assistant 控制娱乐设备,结合 Android 11、APP Flip 增加操作便捷性。与开发者共同打造的 Project Connected Home Over IP ,覆盖主流互联网设备,能搭建更全面的智能家居功能。

%title插图%num

%title插图%num

人才培养是创新关键,今年大会重磅宣布 Codelabs 首次发布中文版,提供手把手的代码教程,让国内开发者们可以在线进行编码实践,学习之路更加顺畅。

 

大会上,谷歌正式宣布和网易有道达成合作,在中国大学 MOOC 上线 Grow with Google 成长计划的学习专区。首发三门课程:TensorFlow 入门实操课程、ARCore 开发入门课程和海外数字营销系列课程,助力人才成长与发展。

 

%title插图%num

来自中央美术学院的邬建安教授,结合 TensorFlow 技术带来了“心面孔”艺术作品,打破艺术品的边界,使一件静止的艺术品,变成了互动的艺术表达。

%title插图%num

%title插图%num

 

包容一向是谷歌关注的议题,无论是此前“谷歌编程女神范 (Google Girl Hackathon)”项目,还是 11 月 19 日的 Google 女性开发者职业发展座谈会、11 月 12 日- 19 日的# ImRemarkable 互动周等活动,都致力于帮助女性展现充满自信的“她力量”。对活动感兴趣,快点击上述链接参与吧!

包容性还体现在开发产品中,通过语音访问、实时字幕等辅助功能,我们鼓励开发者实现无障碍开发,让技术惠及所有用户。

此外,DevFest 在 11 月陆续举行,多个城市的 GDG 社区举办了 GDS viewing party ,让开发者欢聚一堂,共同享受这场科技盛宴。

%title插图%num

看完这些,是不是还意犹未尽?明天,我们将带来 Android 、Google Play 和 Chrome OS 的主题演讲,别忘了 13:00 准时来官网观看哦!连续 5 天,每天 13:00 主题技术演讲等你来!

这么精彩的内容别忘了分享!快带上# Google 开发者大会 #的话题标签,转发这篇推文给需要的朋友吧!

 

 

Android技术迭代更新,到底什么是AndroidX?

Android技术迭代更新很快,各种新出的技术和名词也是层出不穷。不知从什么时候开始,总是会时不时听到AndroidX这个名词,这难道又是什么新出技术吗?相信有很多朋友也会存在这样的疑惑,那么今天我就来写一篇科普文章,向大家介绍AndroidX的前世今生。

%title插图%num
Android系统在刚刚面世的时候,可能连它的设计者也没有想到它会如此成功,因此也不可能在一开始的时候就将它的API考虑的非常周全。随着Android系统版本不断地迭代更新,每个版本中都会加入很多新的API进去,但是新增的API在老版系统中并不存在,因此这就出现了一个向下兼容的问题。

举个例子,当Android系统发布到3.0版本的时候,突然意识到了平板电脑的重要性,因此为了让Android可以更好地兼容平板,Android团队在3.0系统(API 11)中加入了Fragment功能。但是Fragment的作用并不只局限于平板,以前的老系统中也想使用这个功能该怎么办?于是Android团队推出了一个鼎鼎大名的Android Support Library,用于提供向下兼容的功能。比如我们每个人都熟知的support-v4库,appcompat-v7库都是属于Android Support Library的,这两个库相信任何做过Android开发的人都使用过。

但是可能很多人并没有考虑过support-v4库的名字到底是什么意思,这里跟大家解释一下。4在这里指的是Android API版本号,对应的系统版本是1.6。那么support-v4的意思就是这个库中提供的API会向下兼容到Android 1.6系统。它对应的包名如下:

%title插图%num
类似地,appcompat-v7指的是将库中提供的API向下兼容至API 7,也就是Android 2.1系统。它对应的包名如下:

%title插图%num
可以发现,Android Support Library中提供的库,它们的包名都是以android.support.*开头的。

但是慢慢随着时间的推移,什么1.6、2.1系统早就已经被淘汰了,现在Android官方支持的*低系统版本已经是4.0.1,对应的API版本号是15。support-v4、appcompat-v7库也不再支持那么久远的系统了,但是它们的名字却一直保留了下来,虽然它们现在的实际作用已经对不上当初命名的原因了。

那么很明显,Android团队也意识到这种命名已经非常不合适了,于是对这些API的架构进行了一次重新的划分,推出了AndroidX。因此,AndroidX本质上其实就是对Android Support Library进行的一次升级,升级内容主要在于以下两个方面。

*,包名。之前Android Support Library中的API,它们的包名都是在android.support.*下面的,而AndroidX库中所有API的包名都变成了在androidx.*下面。这是一个很大的变化,意味着以后凡是android.*包下面的API都是随着Android操作系统发布的,而androidx.*包下面的API都是随着扩展库发布的,这些API基本不会依赖于操作系统的具体版本。

第二,命名规则。吸取了之前命名规则的弊端,AndroidX所有库的命名规则里都不会再包含具体操作系统API的版本号了。比如,像appcompat-v7库,在AndroidX中就变成了appcompat库。

一个AndroidX完整的依赖库格式如下所示:

implementation ‘androidx.appcompat:appcompat:1.0.2’
1
了解了AndroidX是什么之后,现在你应该放轻松了吧?它其实并不是什么全新的东西,而是对Android Support Library的一次升级。因此,AndroidX上手起来也没有任何困难的地方,比如之前你经常使用的RecyclerView、ViewPager等等库,在AndroidX中都会有一个对应的版本,只要改一下包名就可以完全无缝使用,用法方面基本上都没有任何的变化。

但是有一点需要注意,AndroidX和Android Support Library中的库是非常不建议混合在一起使用的,因为它们可能会产生很多不兼容的问题。*好的做法是,要么全部使用AndroidX中的库,要么全部使用Android Support Library中的库。

而现在Android团队官方的态度也很明确,未来都会为AndroidX为主,Android Support Library已经不再建议使用,并会慢慢停止维护。另外,从Android Studio 3.4.2开始,我发现新建的项目已经强制勾选使用AndroidX架构了。

%title插图%num
那么对于老项目的迁移应该怎么办呢?由于涉及到了包名的改动,如果从Android Support Library升级到AndroidX需要手动去改每一个文件的包名,那可真得要改死了。为此,Android Studio提供了一个一键迁移的功能,只需要对着你的项目名右击 → Refactor → Migrate to AndroidX,就会弹出如下图所示的窗口。

%title插图%num
这里点击Migrate,Android Studio就会自动检查你项目中所有使用Android Support Library的地方,并将它们全部改成AndroidX中对应的库。另外Android Studio还会将你原来的项目备份成一个zip文件,这样即使迁移之后的代码出现了问题你还可以随时还原回之前的代码。

好了,关于AndroidX的内容就讲到这里,相信也是解决了不少朋友心中的疑惑。由于这段时间以来一直在努力赶《*行代码 第3版》的进度,所以原创文章的数量偏少了一些,也请大家见谅。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速