Android面试题总结

1.填充布局

填充布局是很常见的,比如在adapter中,可以里面的一些细节你注意到了吗?
填充布局时我们通常会使用下面的方法

View inflate(Context context, @LayoutRes int resource, ViewGroup root)

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

其实第一个方法最终会调用第二个方法,我们去看看源码

  public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
 //这里调用了第二个方法
        return inflate(resource, root, root != null);
    }

下面说一下最终这个方法的三个参数代表得意思

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
 //参数一
resource:我们要填充的布局文件
//参数二
root:当此参数为null时,直接返回我们填充的布局,并且根布局设置的参数无效,只能显示为wrap_content的样子
    当此参数不为null,设置得跟布局参数有效,如果attachToRoot为false,返回我们需要填充的布局,当attachToRoot为true的时候,加载的视图做为子视图添加到parent视图中,
//参数三
attachToRoot:是否将填充的布局依附到root布局中

我们在使用inflate.inflate的时候,root不能传null,

 @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //填充布局时,最后一个参数root如果为null,那么你条目的根布局的属性也不管用,效果只能是wrap_content
        Log.d(TAG, "onCreateViewHolder: "+parent);
        //直接返回布局,无法实现条目宽度填充屏幕
//        return new MyViewHolder(View.inflate(context,R.layout.list_item_layout,null));
        //下面这句代码报错,java.lang.IllegalStateException: The specified child already has a parent. You must
        // call removeView() on the child's parent first.
//        return new MyViewHolder(inflater.inflate(R.layout.list_item_layout,parent,true));
        //正确代码
        return new MyViewHolder(inflater.inflate(R.layout.list_item_layout,parent,false));
    }

源码

if (TAG_MERGE.equals(name)) { // 判断跟节点是否是merge
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                //如果是merge,把merge的子节点添加到父布局
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                        //如果attachToRoot为false,那么直接返回我们的布局,并通过setLayoutParams设置布局参数
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    //如果attachToRoot为true,通过addView添加布局参数
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }

2.自定义属性
在values下新建attr.xml文件,定义属性名称和类型

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="size" format="dimension"/>
    </declare-styleable>
</resources>
public class CustomView extends View{

    public CustomView(Context context) {
        this(context,null);
    }


    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //获取属性封装到typeArray里面
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        //通过属性名称获取定义的值,格式为自定义View名称_属性名
        int size = typedArray.getInt(R.styleable.CustomView_size, 0);
    }
}

3.SharedPreferences的commit和apply方法
示例代码

SharedPreferences preferences = getSharedPreferences("config", MODE_PRIVATE);
        SharedPreferences.Editor edit = preferences.edit();
        edit.putBoolean("flag",true).commit();
       // edit.putBoolean("flag",true).apply();

如上代码所示,SP有两种提交方式,commit和apply,我们平时用的比较多的是commit,下面来说说两种方式的区别
commit:有返回值,返回true代表提交成功,并且是同步提交
apply:没有返回值,先提交到内存,然后异步提交到磁盘,你无法知道是否提交成功。

当apply未完成的时候,你如果有调用了commit,那么这个commit会阻塞直到apply提交完成。

4.ListView优化以及加载图片错乱原因及解决办法
复用convertview,使用viewholder,分批以及分页加载,在不同状态下做不同处理,比如滑动时才去加载图片,图片压缩,采用缓存,避免在adapter中使用static定义全局变量,使用getApplicationContext,避免在ListView中使用线程

错乱原因:ListView使用了缓存的convertview,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候,其实该item已经不在当前显示区域内了,此时显示的后果将可能在第十个item上输出图像,这就导致了图片错位的问题。
解决办法:1.加载图片之前给图片设置一个tag为图片地址url,加载图片的时候根据getTag方法判断结果是否和之前的url相等。
2.使用弱引用关联
两种方法详细介绍请见下面的链接
ListView加载图片错乱问题及解决办法

5.Handler处理消息的方式的区别(sendMessage和post方法)
//post方式

  new Handler().post(new Runnable() {
            @Override
            public void run() {

            }
        });

//send方式

  new Handler(){
          @Override
          public void handleMessage(Message msg) {
              super.handleMessage(msg);
          }
      }.sendMessage(msg);

下面从源码的角度讲一下两个的区别,我们先看post

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

此处将我们的Runnable对象封装成了一个消息,然后调用的也是send方法

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

这里我们重点关注第二句,将r赋值给了消息的callback属性,
sendMessage方法的最终结果是把消息插入到了消息队列中,然后就该Looper不断从消息队列取出消息进行处理了,我们看看相关源码,在Looper的loop方法中。

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);
            ....
            }

msg.target.dispatchMessage(msg)这句是重点,msg.target是一个Handler对象,我们去看看dispatchMessage方法,这个方法就是进行最终的消息处理。

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

首先判断msg的callback是不是为空,当我们使用post方式的时候callback就不为空,这时候调用handleCallback方法

private static void handleCallback(Message message) {
        message.callback.run();
    }

这里很好理解,调用了我们runnable中的run方法进行了消息的处理。
接下来判断mCallback 是不是为空,那么这个mCallback是啥,是创建Handler时的一个参数,不过我们很少用,这里就不详细说了,默认我们都是用空的构造方法,所以这里mCallback为空,最终会调用我们重写的handlemessage方法。到此,源码分析完毕

6.线程生命周期

这里写图片描述

上图有一个例外,调用yield()方法可以让当前处于运行状态的线程转入就绪状态。如果要测试某线程是否已经死亡,可以使用isAlive()方法,该方法在线程处于就绪、运行、阻塞时放回true,新建和死亡时返回false。不要试图对一个已经死亡的线程调用start()方法而重新启动,死亡就是死亡和人一样,不可能再生。还有也不要对一个线程调用两次start()方法,这同样会引发异常。

7.同样在Activity中显示视图,fragment和View的区别
fragment有完整的生命周期,从代码设计角度讲可以提高内聚性,方便view的管理
如果都用view,全部写在Activity中,耦合性高,用fragment可以降低耦合度。

简单的用view,复杂的用fragment,fragment消耗资源比较大

fragment可以做一些特殊的操作,比如当Activity重新启动时,保存数据

8.Fragment用来保存数据
没有界面的fragment可以用来保存相对大的数据,比如bitmap对象,

public class CustomFragment extends Fragment {
    private static final String TAG = "CustomFragment";
    private Bitmap bitmap;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置此方法,当Fragment销毁和重建时,会跳过onDestory和onCreate,默认为false
        setRetainInstance(true);
        Log.d(TAG, "onCreate: ");
    }
    public void setData(Bitmap bitmap){
        this.bitmap = bitmap;
    }

    public Bitmap getBitmap(){
        return bitmap;
    }
}
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private ImageView imageView;
    private Bitmap bitmap;
    private CustomFragment customfragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageview);
        FragmentManager manager = getSupportFragmentManager();
        customfragment = (CustomFragment) manager.findFragmentByTag("cus");
        if(customfragment == null){
            customfragment = new CustomFragment();
            manager.beginTransaction().add(customfragment,"cus").commit();
        }
        bitmap = customfragment.getBitmap();
        initData();
    }

    public void initData(){
        if(bitmap == null){
            Toast.makeText(this, "加载图片", Toast.LENGTH_SHORT).show();
            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img1);
        }
        imageView.setImageBitmap(bitmap);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
        customfragment.setData(bitmap);
    }
}

上面代码实现的功能是当Activity重建的时候,我们不需要重新去加载图片,而是从fragment中取出保存的图片,设置给imageview.在fragment的onCreate方法中,我们调用了setRetainInstance(true)方法,这个方法的作用就是指定当fragment所在的Activity销毁时,fragment不进行销毁,代码上的体现是当Activity销毁和重建时,fragment会跳过自己生命周期的onDestory和onCreate方法。
基于上面的步骤,然后我们在Activity中获取fragment实例的时候,不管Activity重建了几次,获得的都是同一个fragment对象,所以fragment里面对应的数据也不会丢失,就起到了保存数据的作用。
下面的文章讲解了fragment的非中断保存。
fragment非中断保存数据

9.fragment生命周期
这里写图片描述

10.在不压缩图片的情况下,加载高清大图
1.使用webView加载
2.TileView
3.Android 高清加载巨图方案 拒绝压缩图片

11.两个Activity之间跳转时的生命周期
假如有下面的场景,A中启动了B,然后按返回键退出B,通过实验生命周期方法如下。
这里写图片描述
总之记住一点就可以,下一个Activity执行了onResume之后,当前Activity的onStop方法才会调用。

12.Android数据库版本升级及数据的迁移
数据库版本升级及数据的迁移

13.进程和线程
进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程

14.如何监听多个线程执行任务完毕
假设我们要三个线程分别去下载各自对应的图片,在所有任务结束后需要给出一个提示,那么现在的问题是我们如何监听所有线程执行完毕了呢,下面给出答案。

效果图
这里写图片描述

我们要监听图中三个线程全部到达100的那个时候,下面给出两种解决方案。
a:使用信号量Semaphore

public class SplashActivity extends AppCompatActivity {
    private static final String TAG = "SplashActivity";
    private static final int THREAD1_START = 1;
    private static final int THREAD2_START = 2;
    private static final int THREAD3_START = 3;
    private static final int THREAD_FINISH = 4;
    private TextView tv1;
    private TextView tv2;
    private TextView tv3;
    private ProgressBar pb1;
    private ProgressBar pb2;
    private ProgressBar pb3;
    ExecutorService pool = Executors.newFixedThreadPool(3);
    private int num1 = 0;
    private int num2 = 0;
    private int num3 = 0;
    final Semaphore semp = new Semaphore(3);
    private int count = 0;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int result = msg.arg1;
            switch (msg.what){
                case THREAD1_START:
                    pb1.setProgress(result);
                    tv1.setText("线程一:"+result);
                    break;
                case THREAD2_START:
                    pb2.setProgress(result);
                    tv2.setText("线程二:"+result);
                    break;
                case THREAD3_START:
                    pb3.setProgress(result);
                    tv3.setText("线程三:"+result);
                    break;
                case THREAD_FINISH:
                        Toast.makeText(SplashActivity.this, "任务执行完毕", Toast.LENGTH_SHORT).show();
            }
        }
    };
    private Future<Integer> task1;
    private Future<Integer> task2;
    private Future<Integer> task3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        initView();
    }
    public void initView(){
        tv1 = (TextView) findViewById(R.id.tv1);
        tv2 = (TextView) findViewById(R.id.tv2);
        tv3 = (TextView) findViewById(R.id.tv3);
        pb1 = (ProgressBar) findViewById(R.id.pb1);
        pb2 = (ProgressBar) findViewById(R.id.pb2);
        pb3 = (ProgressBar) findViewById(R.id.pb3);
    }



    public void click(View view){
        switch (view.getId()){
            case R.id.btn_start_one:
                if(pb1.getProgress() == pb1.getMax()){
                    num1 = 0;
                }
                startThread1();
                break;
            case R.id.btn_start_two:
                if(pb2.getProgress() == pb2.getMax()){
                    num2 = 0;
                }
                startThread2();
                break;
            case R.id.btn_start_three:
                if(pb3.getProgress() == pb3.getMax()){
                    num3 = 0;
                }
                startThread3();
                break;
        }
    }


    public void startThread1(){
        if(task1!=null&&!task1.isDone()){
            return;
        }
        Callable<Integer> callable1 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                semp.acquire();
                while (num1<100){
                    num1++;
                    sendMsg(num1,THREAD1_START);
                    Thread.sleep(50);
                }
                semp.release();
                count = semp.availablePermits();
                checkFinish(count);
                return 100;
            }
        };
        task1 = pool.submit(callable1);
    }
    public void startThread2(){
        if(task2!=null&&!task2.isDone()){
            return;
        }
        Callable<Integer> callable2 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
            //当前线程获取信号量
                semp.acquire();
                while (num2<100){
                    num2++;
                    sendMsg(num2,THREAD2_START);
                    Thread.sleep(50);
                }
                //执行完毕释放
                semp.release();
                //当前可用数量
                count = semp.availablePermits();
                 checkFinish(count);
                return 100;
            }
        };
        task2 = pool.submit(callable2);
    }
    public void startThread3(){
        if(task3!=null&&!task3.isDone()){
            return;
        }
        final Callable<Integer> callable3 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                semp.acquire();
                while (num3<100){
                    num3++;
                    sendMsg(num3,THREAD3_START);
                    Thread.sleep(50);
                }
                semp.release();
                count = semp.availablePermits();
                checkFinish(count);
                return 100;
            }
        };
        task3 = pool.submit(callable3);
    }
    public void sendMsg(int num,int what){
        Message msg = Message.obtain();
        msg.what = what;
        msg.arg1 = num;
        mHandler.sendMessage(msg);
    }

    public void checkFinish(int count){
        if(count ==3){
            sendMsg(count,THREAD_FINISH);
        }
    }
}

讲一下大概的思路:Semaphore可以用来控制资源同时访问的个数,这里我们初始化个数为3,然后当前线程执行的时候,获取信号量,执行完毕释放信号量,每个线程执行完毕后,我们去检查当前可用个数,当所有线程执行完毕后,可以个数应该为3,我们利用这点去判断。

b.使用线程组
先说一下线程组和线程池的区别,线程池的主要作用是方便管理线程,复用线程,而线程组的优势在于可以对线程进行遍历,知道哪些线程已经运行完毕,哪些线程还在运行。

为了配合线程组的使用,我们把代码做下修改,实现效果不变

public class SplashActivity extends AppCompatActivity {
    private static final String TAG = "SplashActivity";
    private static final int THREAD1_START = 1;
    private static final int THREAD2_START = 2;
    private static final int THREAD3_START = 3;
    private static final int THREAD_FINISH = 4;
    private TextView tv1;
    private TextView tv2;
    private TextView tv3;
    private ProgressBar pb1;
    private ProgressBar pb2;
    private ProgressBar pb3;
    private int num1 = 0;
    private int num2 = 0;
    private int num3 = 0;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int result = msg.arg1;
            switch (msg.what) {
                case THREAD1_START:
                    pb1.setProgress(result);
                    tv1.setText("线程一:" + result);
                    break;
                case THREAD2_START:
                    pb2.setProgress(result);
                    tv2.setText("线程二:" + result);
                    break;
                case THREAD3_START:
                    pb3.setProgress(result);
                    tv3.setText("线程三:" + result);
                    break;
                case THREAD_FINISH:
                    Toast.makeText(SplashActivity.this, "任务结束", Toast.LENGTH_SHORT).show();
            }
        }
    };
    private ThreadGroup threadGroup;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        threadGroup = new ThreadGroup("downloadthread");
        initView();
    }

    public void initView() {
        tv1 = (TextView) findViewById(R.id.tv1);
        tv2 = (TextView) findViewById(R.id.tv2);
        tv3 = (TextView) findViewById(R.id.tv3);
        pb1 = (ProgressBar) findViewById(R.id.pb1);
        pb2 = (ProgressBar) findViewById(R.id.pb2);
        pb3 = (ProgressBar) findViewById(R.id.pb3);
    }

    public void click(View view) {
        switch (view.getId()) {
            case R.id.btn_start_one:
                if (pb1.getProgress() == pb1.getMax()) {
                    num1 = 0;
                }
                startThread1();
                break;
            case R.id.btn_start_two:
                if (pb2.getProgress() == pb2.getMax()) {
                    num2 = 0;
                }
                startThread2();
                break;
            case R.id.btn_start_three:
                if (pb3.getProgress() == pb3.getMax()) {
                    num3 = 0;
                }
                startThread3();
                break;
        }
    }

    public void startThread1() {
    //创建线程的时候,指定它所在的线程组
        MyThread myThread = new MyThread(threadGroup, "thread1", num1,THREAD1_START);
        myThread.start();
    }

    public void startThread2() {
        MyThread myThread = new MyThread(threadGroup, "thread2", num2,THREAD2_START);
        myThread.start();
    }

    public void startThread3() {
        MyThread myThread = new MyThread(threadGroup, "thread3", num3,THREAD3_START);
        myThread.start();
    }

    public void sendMsg(int num, int what) {
        Message msg = Message.obtain();
        msg.what = what;
        msg.arg1 = num;
        mHandler.sendMessage(msg);
    }
    //检查是否全部执行完毕
    public void checkFinish(int count) {
        if (count == 1) {
            sendMsg(count, THREAD_FINISH);
        }
    }

    class MyThread extends Thread {
        private int num;
        private int threadnumber;
        MyThread(ThreadGroup tg, String name, int num,int threadnumber) {
            super(tg, name);
            this.num = num;
            this.threadnumber = threadnumber;
        }

        @Override
        public void run() {
            super.run();
            while (num < 100) {
                num++;
                sendMsg(num, threadnumber);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           checkFinish(threadGroup.activeCount());
        }
    }
}

15.布局优化

  • merge减少布局层次
  • include重用布局
  • ViewStub延迟加载

16.实现多线程两种方式比较
使用Runnable接口
实际工作中,几乎所有的多线程应用都用实现Runnable这种方式。
Runnable适合多个相同程序代码的线程去处理同一资源的情况。把虚拟CPU(线程)同程序的代码、数据有效的分离,较好的体现了面向对象的设计思想。
避免由于Java的单继承特性带来的局限性。也就是如果新建的类要继承其他类的话,因为JAVA中不支持多继承,就只能实现java.lang.Runnable接口。
有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

继承Thread类
不能再继承他类了。
编写简单,可以直接操纵线程,无需使用Thread.currentThread()

17.解析XML的方式区别
1、【DOM】
DOM是基于树的结构,通常需要加载整文档和构造DOM树,然后才能开始工作。
优点:
a、由于整棵树在内存中,因此可以对xml文档随机访问
b、可以对xml文档进行修改操作
c、较sax,dom使用也更简单。
缺点:
a、整个文档必须一次性解析完
a、由于整个文档都需要载入内存,对于大文档成本高
2、【SAX】
SAX类似流媒体,它基于事件驱动的,因此无需将整个文档载入内存,使用者只需要监听自己感兴趣的事件即可。
优点:
a、无需将整个xml文档载入内存,因此消耗内存少
b、可以注册多个ContentHandler
缺点:
a、不能随机的访问xml中的节点
b、不能修改文档
18.线程池是如何做到线程复用的?
19.在什么时机能够获取到view的宽高,为什么在onCreate中获取不到View的尺寸
答:因为View的绘制流程是从ViewRootImpl的performTraversals方法开始的,具体可以参考我的这篇文章Activity启动流程以及View的绘制流程,而ViewRootImpl的创建是在ActivityThread的handleResumeActivity方法中的,而handleResumeActivity会调用Activity的onResume方法,所以说在onCreate中无法获取View的尺寸,因为此时View还没有开始绘制。至于如何正确获取View的尺寸大家自行百度。这里不是重点。
20.wait和sleep,yield方法
答:首先说说wait和sleep的区别:
(1)来自不同的类,wait来自Object,而sleep是Thread的静态方法。
(2)sleep没有释放锁,wait释放了锁,使得其他线程可以进入同步代码块或者方法
(3)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
(4)sleep必须捕获异常,而wait不需要
线程状态:在JDK5.0及其以上版本中,线程总共有6种状态:
NEW(新建状态,初始化状态),RUNNABLE(可运行状态,就绪状态),BLOCKED(阻塞状态,被中断运行),WAITTING(等待状态),TIMED_WAITTING(定时等待状态),TERMINATED(死亡状态,终止状态)

这些状态被定义在Thread中的枚举类State中

public enum State {  
    NEW,  
    RUNNABLE,  
    BLOCKED,  
    WAITING,  
    TIMED_WAITING,  
    TERMINATED;  
} 

21.结束线程的方法
答案:结束线程方法
22.dp和sp,为什么要用dp和sp
答案:为什么要用dp
sp的作用
23.自己设计线程池需要考虑什么
答:我们可以利用ThreadPoolExecutor这个类来实现自己的线程池:
它的构造方法如下:

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

下面说一下每个参数的含义:
corePoolSize:核心线程数,
maximumPoolSize:线程池所能容纳的最大线程数,
keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收
unit:超时时间的单位
workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。
threadFactory:线程工厂,为线程池提供创建新线程的功能

24.AsyncTask的实现
Android异步处理框架AsyncTask源码解析
25.HashMap的原理
26.ImageView的src和background区别
答案:src和background区别
27.Fragment的replace和hide区别
答:replace会重新创建Fragment实例,官方建议只有上一个Fragment不再使用时才用replace,hide不会创建新的fragment实例,每次切换不会执行生命周期方法,可以用onHiddenChanged方法进行监听。
30.软引用和弱引用
Java 7之基础 - 强引用、弱引用、软引用、虚引用
31.Scroller的原理
Scroller使用
32.onNewIntent何时被触发,Activity启动模式
关于Activity的onNewIntent方法
33.wait和sleep都可以控制线程,为什么wait要设计在Object中
34.try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?
答:在return之前。
35.android:layout_weight 在match_parent、wrap_content、0dp时的不同情况
首先说说layout_weight的意义:android:layout_weight的真实含义是:一旦View设置了该属性(假设有效的情况下),那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比!

下面这张图是自己做实验所得出,大家可以看出不同情况下的效果。
这里写图片描述

上面实验的前提是水平方向上第一个按钮的layout_weight为1,第二个为2

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值