Android常见问题总结(二)

1.冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。

思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。

第一趟比较完成后,最后一个数一定是数组中最大的一个数,所以第二趟比较的时候最后一个数不参与比较;

第二趟比较完成后,倒数第二个数也一定是数组中第二大的数,所以第三趟比较的时候最后两个数不参与比较;

依次类推,每一趟比较次数-1;

由此可见:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数,即

for(int i=1;i<arr.length;i++){

    for(int j=1;j<arr.length-i;j++){

    //交换位置

}

2.写个快排

package com.company;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        // write your code here
        int[] arr = {99, 8, 7, 13, 45, 43, 75, 223, 9, 14, 57,76, 35, 4, 11, 2, 1};
        quickSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void quickSort(int[] arr) {
        rePartArrays(arr, 0, arr.length - 1);
    }

    public static int partArr(int arr[], int start, int end) {
        // 选取基准元素,这里以最后一个为准
        int base = arr[end];
        // 假设比较的元素都比基准元素大 n=0
        int n = start;
        // 基准元素不参与比较
        for (int i = start; i < end; i++) {
            if (base > arr[i]) {
                if (i != n) {
                    // 小于基准元素的交换位置
                    exchangeValue(arr, i, n);
                }
                n++;
            }
        }
        // 便利完成后交换基准元素
        exchangeValue(arr, n, end);
        return n;
    }


    /**
     * 交换数组中的两个元素
     */
    public static void exchangeValue(int[] arr, int index1, int index2) {
        int temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }

    public static void rePartArrays(int[] arr, int start, int end) {
        // 递归调用的结束条件,开始要拆分的数组就剩下一个元素的时候
        if (end - start < 1) {
            return;
        }
        int part = partArr(arr, start, end);
        // 三种情况下的继续拆分
        if (part == start) {
            rePartArrays(arr, part + 1, end);
        } else if (part == end) {
            rePartArrays(arr, start, end - 1);
        } else {
            rePartArrays(arr, start, part - 1);
            rePartArrays(arr, part + 1, end);
        }
    }

}

3.写个单例模式

双重锁:

public class Singleton {

    /**
     * 对保存实例的变量添加volatile的修饰
     */

    private volatile static Singleton instance = null;

    private Singleton(){

    }

    public static Singleton getInstance(){

//先检查实例是否存在,如果不存在才进入下面的同步块

        if(instance == null){

//同步块,线程安全的创建实例

            synchronized(Singleton.class){

//再次检查实例是否存在,如果不存在才真的创建实例

                if(instance == null){

                    instance = new Singleton();

                }

            }

        }

        return instance;

    }

}

Holder模式: 

public class Singleton {
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }
    /**
     * 私有化构造方法
     */
    private Singleton(){
    }
    public static  Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

推荐这两种,其他方式不推荐。

 

4.异常生命周期

onCreate()->onStart()->onResume()->onPause()->onSaveInstanceState()->onStop()->

onDestroy()->onCreate()->onStart()->onRestoreInstanceState()->onResume()

建议在onCreate()和onRestoreInstanceState()中恢复数据

 

5.从点击应用到进入应用,Android系统都做了哪些工作,期间涉及到的进程切换有哪些?

 

Activity 的整体启动流程如图所示:

 

6.说说你了解的IPC方法

1.Bundle,用于Android四大组件间的进程间通信

2.文件共享,建议单线程使用

3.Messenger

1. 首先是客户端A发送消息给服务端B  所以在客户端A中 声明一个Handler用来接受消息  并创建一个Messenger对象 用Handler作为参数构造  然后onBinder方法返回messenger.getBinder() 即可

public class MyServiceA extends Service {
 
		private class MessageHandler extends Handler{  //创建的接受消息的handler
			@Override
			public void handleMessage(Message msg) {
				switch (msg.what){
					case 1:
						Bundle bundle = msg.getData();
						String str = bundle.getString("aaa");
						System.out.println("----"+str);
						Messenger replyTo = msg.replyTo; //此处往下是用来回复消息给客户端A的   
						Message replyMsg = Message.obtain(null,2);
						Bundle bundle1 = new Bundle();
						bundle1.putString("bbb","remote222给主进程回复消息啦");
						replyMsg.setData(bundle1);
						try {
							replyTo.send(replyMsg);
						} catch (RemoteException e) {
							e.printStackTrace();
						}
						break;
				}
				super.handleMessage(msg);
			}
		}
		Messenger messenger = new Messenger(new MessageHandler());
		public MyServiceA() {
		}
		
		public IBinder onBind(Intent intent) {
			return messenger.getBinder();
		}
	}

2.在客户端A自然是需要发送消息给服务端B的  所以需要在服务绑定完成之后  获取到binder对象  之后用该对象构造一个Messenger对象  然后用messenger发送
消息给服务端即可  代码如下  :

public void onServiceConnected(ComponentName name, IBinder service) {
                Messenger messenger = new Messenger(service);
                Message msg = Message.obtain(null,1);
                Bundle bundle = new Bundle();
                bundle.putString("aaa", "主进程给remote22进程发消息啦");
                msg.setData(bundle);
                msg.replyTo = mmessenger; //这行代码用于客户端A接收服务端请求 设置的消息接收者 
                try {
                    messenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
 
            }

 3.由于在服务端接收到了客户端的消息还需要回复  所以在服务端代码中获取 msg中的replyTo对象  用这个对象发送消息给 客户端即可 
 在客户端需要创建一个handler和Messenger  将发送的msg.replyTo设置成Messenger对象  就可  

4.AIDL

基本数据类型---- int long  char  boolean double 
String  charSequence
List  只支持ArrayList  CopyOnWriteArrayList也可以。。  里面元素也必须被aidl支持
Map   只支持HashMap   ConCurrentHashMap也可以  里面元素也必须支持aidl
Parcelable  所有实现了此接口的对象 
AIDL  所有的AIDL接口   因此 如果需要使用接口 必须使用AIDL接口

在RemoteCallBackList中封装了一个Map 专门用来保存所有的AIDL回调  key为IBinder  value是CallBack   使用IBinder 来区别不同的对象  ,
因为跨进程传输时会产生很多个不同的对象  但这些对象的底层的Binder都是同一个对象  所以可以  
在使用RemoteCallBackList时 add 变为 register  remove 变为 unregister  遍历的时候需要先 beginBroadcast  这个方法同时也获取集合大小 
获取集合中对象使用 getBoardCastItem(i)  最后不要忘记finishBoardCast方法   

还有一个情况  由于onServiceConnected方法 是在主线程执行的  如果在这里执行服务端的耗时代码  会ANR  所以需要开启一个子线程执行  
同理在服务端中 也不可以运行客户端的耗时程序  
总结起来就是 在执行其他进程的耗时程序时  都需要开启另外的线程防止阻塞UI线程  如果要访问UI相关的东西  使用handler  

为了程序的健壮性  有时候Binder可能意外死亡  这时候需要重连服务  有2种方法:
1.在onServiceDisconnected方法中  重连服务  
2. 给Binder注册DeathRecipient监听  当binder死亡时 我们可以收到回调  这时候我们可以重连远程服务
 

最后有时候我们不想所有的程序都可以访问我们的远程服务  所以可以给服务设置权限和过滤:
1.我们在onbind中进行校验 用某种方式 如果验证不通过那么就直接返回null 
2.我们可以在服务端的AndroidMiniFest.xml中  设置所需的权限  <permission android:name="aaaaaa" android:protectionLevel="normal"/>
然后在onbind中 检查是否有这个权限了  如果没有那么直接返回null即可  判断方法如下  :

int check = checkCallingOrSelfPermission("aaa");
			if(check== PackageManager.PERMISSION_DENIED){
				return null;
			}

 

5.ContentProvider

此方法使用起来也比较简单  底层是对Binder的封装 使之可以实现进程间通信  使用方法如下  
1. 在需要共享数据的应用进程中建立一个ContentProvider类 重写它的CRUD 和getType方法  在这几个方法中调用对本应用进程数据的调用 ,然后在AndroidMinifest.xml文件中声明provider  

 <provider 
				android:authorities="com.yangsheng.book"  //这个是用来标识provider的唯一标识  路径uri也是这个
				android:name=".BookProdiver"
				android:process=":remote_provider"/>   //此句为了创建多进程  正常不需要使用

2. 在需要获取共享数据的应用进程中调用getContentResolver().crud方法  即可实现数据的查询  

6.Socket

7.Binder连接池

我们在android中进程间通信 一般都使用 AIDL实现 因为它强大  但是普通的使用方法每次使用AIDL 都需要开启一个服务  如果有多个AIDL请求 那岂不是要开启很多个服务 
这明显是不可以的  比如你让你用户的手机 发现你这一个应用程序绑定了10个服务  那是极差的  所以 我们在多个AIDL 请求的时候可以使用Binder连接池技术 
只开启一个服务  根据需要获取的AIDL不同 转化成需要的AIDL 接口 执行不同的方法  
实现的基本原理  就是在onbind中返回一个BinderPool 接口 这个接口有个方法 可以根据不同的标志位返回不同的aidl接口  这样我们在asInTerface之后调用哪个方法
传入标志位即可返回需要的aidl接口 

1.假设原来有2个AIDL接口需要实现(可以扩展成多个)  在服务端建立好AIDL文件  并且建立一个IBinderPool aidl接口  只有一个查询binder的方法 用于查询需要的binder

interface IBinderPool {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
IBinder queryBinder(int code);  //此方法返回Ibinder  用于转化成需要的AIDL接口
}


2.在服务端 onbind方法中返回 IBinderPool的实现类  实现query方法 按照传入的code 返回需要的ibinder

@Override
public IBinder onBind(Intent intent) {
return iBinderPool;
}


private Binder iBinderPool = new IBinderPool.Stub() {


@Override
public IBinder queryBinder(int code) throws RemoteException {
switch (code) {
case 1:
return new IBookManger.Stub() {


@Override
public void getBook() throws RemoteException {
System.out.println("--->book");
}
};
case 2:
return new IPersonManager.Stub() {


@Override
public void getPerson() throws RemoteException {
System.out.println("---->person");
}
};


}
return null;
}
};


3.客户端实现一个BinderPool类  这个类主要是封装了 AIDL的一些实现方法 方便调用罢了 其中 涉及到一个可以实现同步机制的类
CountDownLatch  这个类 当他的值 不是0的时候  执行了await方法后会使方法一直停在await处  不进行 直到他的值变成了0 才可以继续执行  
也就是说 当执行了await方法  这个线程就会阻塞 等待这个数值变到0后继续执行  
而在BinderPool中的应用场景是这样的   
 

private void connectService(){
countDownLatch = new CountDownLatch(1);  //实现同步机制 
Intent intent = new Intent();
intent.setClass(ctx,MyService.class);
ctx.bindService(intent,connection,Context.BIND_AUTO_CREATE);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


首先为什么在这里要使用同步机制  我们要搞清楚 让我们看这个方法调用的时机 :

binderPool = BinderPool.getInstance(MainActivity.this);  //connectService方法 是在这个方法中调用的  
                IBinder iBinder = binderPool.queryBinder(2);
                iPersonManager = IPersonManager.Stub.asInterface(iBinder);
                try {
                    iPersonManager.getPerson();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }


因为我们最终的目的是在bind服务  连接到远程服务之后获取到 binderPool对象调用它的 binderPool.queryBinder(2) 方法  如果不加同步机制
异步执行  就有可能在 connectService方法 执行完之后  执行IBinder iBinder = binderPool.queryBinder(2);这行代码的时候binderPool对象还
没有被赋值  这样就会产生问题  所以我们让 connectService方法 阻塞  当BinderPool中的 binderPool对象赋值之后 让CountDownLatch的值countDown到0
这样 connectService方法就会继续执行 然后执行下一行代码了  

总结:

1.仅仅是跨进程的四大组件间的传递数据时,使用Bundle就可以,简单方便

2.当需要共享一个应用程序的内部数据时,使用ContentProvider比较方便

3.当并发程度不高,也就偶尔一两次的那种,进程间通信用Messenger就可以

4.当设计网络数据共享时,使用socket就可以

5.当需求比较复杂,高并发,并且还要求实时通信,而且有RPC的需求时,就得使用AIDL了

6.文件共享用于一些缓存共享之类的

 

7.Service与Activity的交互

1.Binder

Activity bindService中的ServiceConnection可以得到一个Service对象从而获取service的方法

2.广播

 

8.Android线程池的原理

ThreadPoolExecutor

其中参数最多的构造方法如下:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        ...
    }

corePoolSize: 该线程池中核心线程的数量。

maximumPoolSize:该线程池中最大线程数量。(区别于corePoolSize)

keepAliveTime:从字面上就可以理解,是非核心线程空闲时要等待下一个任务到来的时间,当任务很多,每个任务执行时间很短的情况下调大该值有助于提高线程利用率。注意:当allowCoreThreadTimeOut属性设为true时,该属性也可用于核心线程。

unit:上面时间属性的单位

workQueue:任务队列,后面详述。

threadFactory:线程工厂,可用于设置线程名字等等,一般无须设置该参数。

各个线程池总结及适用场景:

newCachedThreadPool:

底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool:

底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景

NewScheduledThreadPool:

底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景

 

9.双线程通过线程同步的方式打印12121212...

public class Main {

    static final Object lock = new Object();

    public static void main(String[] args) {

        // 线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (lock) {
                        try {
                            // 睡眠1秒后打印
                            Thread.sleep(1000);
                            System.out.println("1");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 唤醒线程2
                        lock.notify();
                        try {
                            //线程1进入等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

        // 线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (lock) {
                        try {
                            Thread.sleep(1000);
                            System.out.println("2");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 唤醒线程1
                        lock.notify();
                        try {
                            // 线程2进入等待
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }
        }).start();

    }
}

还有一种方式:标记变量法

public class Test {

    volatile static boolean flag = true;

    public static void main(String[] args) {

        Thread t1 = new MyThread(1);
        Thread t2 = new MyThread(2);
        t1.start();
        t2.start();

    }

    static class MyThread extends Thread {

        int pointValue;

        public MyThread(int pointValue) {
            this.pointValue = pointValue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    // 睡眠1秒
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (pointValue == 1) {
                    if (flag) {
                        System.out.println("1");
                        flag = !flag;
                    }
                } else if (pointValue == 2) {
                    if (!flag) {
                        System.out.println("2");
                        flag = !flag;
                    }
                }
            }
        }
    }
}

 

参考博文链接:

Android实现IPC的几种方式:https://blog.csdn.net/u012760183/article/details/51397014#

Android线程池原理:https://www.jianshu.com/p/7b2da1d94b42

Android线程池原理:https://blog.csdn.net/l540675759/article/details/62230562

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android OTA(Over-The-Air)升级是指通过网络传输直接在设备上进行系统升级的一种方法。下面简要介绍Android OTA升级的流程以及常见问题分析。 Android OTA升级流程通常包括以下几个步骤: 1. 系统准备:开发者根据新版本的要求,为设备准备升级所需的系统文件。这些文件包括操作系统文件、应用程序文件以及升级脚本等。 2. 设备请求:设备通过连接到网络,并向服务器请求最新的系统升级。服务器根据设备的型号和当前系统版本,判断是否需要进行升级。 3. 传输升级文件:如果服务器确认设备需要进行升级,那么就会将升级文件传输给设备。这通常是通过HTTP或FTP等协议进行数据传输。 4. 升级验证:设备接收到升级文件后,会进行文件校验,以确保文件的完整性和一致性。 5. 升级安装:设备在校验通过后,会进行系统升级安装。这通常会涉及到文件解压、系统分区扩展等操作。 6. 安装后处理:升级完成后,设备会重新启动并进行一些后期处理工作,例如数据迁移、应用优化等。 常见问题分析: 1. 升级失败:升级过程中可能出现各种原因导致升级失败,如网络中断、升级文件损坏、设备存储空间不足等。解决方法可以包括重新尝试升级、检查网络连接、清理设备存储空间等。 2. 兼容性问题:新版本的系统可能对设备硬件或软件要求更高,而一些老旧设备可能无法满足这些要求。这可能导致升级后设备性能下降或功能不可用。解决方法可以是提供适配的系统版本或更新设备硬件。 3. 数据丢失:升级过程中可能导致设备的数据丢失,包括联系人、短信、应用程序数据等。为了避免这种问题,可以提醒用户备份数据或提供数据迁移工具。 4. 升级时间过长:升级过程可能需要较长时间,尤其是在升级文件较大或设备性能较低的情况下。解决方法可以是优化升级文件的大小和传输速度,或提供快速升级选项。 总结Android OTA升级通过网络直接在设备上进行系统升级,流程包括准备、请求、传输、验证、安装和处理。常见问题包括升级失败、兼容性问题、数据丢失和升级时间过长等,需要通过合适的解决方法来处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值