描述:一个应用,首次安装应用黑屏5秒左右后才开始显示正常界面。在做桌面应用的时候,由于桌面一直被用所以也没怎么发现,而且该问题是只有每次卸载(或者之前没有该应用)之后再次安装首次启动才会出现黑屏。后来经过打印时间才定位到是因为初始化的时候获取IMEI耗时了10s多(在界面设置要显示的View之前).

获取IMEI(设备ID):Requires Permission: READ_PHONE_STATE

TelephonyManager tm = (TelephonyManager) mContext
			.getSystemService(Service.TELEPHONY_SERVICE);
	String deviceId = tm.getDeviceId();

 查看源码:

622     public String More ...getDeviceId() {
623         try {
624             return getITelephony().getDeviceId();
625         } catch (RemoteException ex) {
626             return null;
627         } catch (NullPointerException ex) {
628             return null;
629         }
630     }

 

2369    private ITelephony More ...getITelephony() {
2370        return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
2371    }

 好吧,AIDL实现的,比较底层,木有去看,不过也猜出了个一二了,因为初始化的时候直接在主线程里去获取IMEI,而获取IMEI是需要权限的,这个时候系统会弹出个框让你选择是否授权,一般当用户点击的时候已经过了系统判断的时间了,于是又等10秒再去判断(当然这只是结合以为大神和我自己的看法),这就导致了主线程需要10s才获得IMEI,获取到IMEI之后才去设置要显示的View,所以之前就出现了黑屏。

 

解决:在初始化的时候开启一个线程去获取,当获取到值的时候再回调给需要IMEI的地方。但是这里获取IMEI的时间不一,可能要几毫秒,也可能是10多秒。再者就是不同的时间去获取,等待的时间不一,可能先获取的地方等待的时间更长,所以,干脆每次需要获取的时候都去开启一个线程获取,于是便有了以下设计

(为什么不是直接用一个子线程去获取然后赋值?耗时长,多次请求可以加大概率,而且可能不同的地方需要获得该值),如果是只有一个地方需要初始化的话,可以只要单独一个线程去获取就好。

public class MainActivity extends Activity {
	private Context mContext;
	private String mImei;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mContext = this;
		// 初始化获取IMEI
		IMEIUtils.getInstance().init(mContext);

		// 其他事情
		// 其他事情

		// 在需要的地方获取IMEI;
		mImei = IMEIUtils.getInstance().getImei();
		if (IMEIUtils.getInstance().isImeiGetting(mImei)) {// 如果是正在获取当中
			mImei = IMEIUtils.getInstance().getNullImei();// 先给他初始化一个空值的
			// 添加监听
			IMEIUtils.getInstance().addOnImeiGetListener(new ImeiGetListener() {

				@Override
				public void onGetIMEIOk(String imei) {// 得到之后将回调其值给需要的地方
					mImei = imei;
				}
			});
		}
	}
}

 

public class IMEIUtils {
	private static final String TAG = "IMEIUtils";
	private static final String IMEI_RESULT_GETTING = "imei_result_getting";
	private String mIMEI = "";
	private ArrayList<ImeiGetListener> mEncodeImeiListenerList = new ArrayList<ImeiGetListener>();
	private static IMEIUtils mEncodeImeiUtils = null;
	private Context mContext;

	private IMEIUtils() {
	}

	public synchronized static IMEIUtils getInstance() {
		if (mEncodeImeiUtils == null) {
			mEncodeImeiUtils = new IMEIUtils();
		}
		return mEncodeImeiUtils;
	}

	/**
	 * 初始化
	 * 
	 * @param context
	 */
	public void init(Context context) {
		Log.d(TAG, "initImei");
		mContext = context;
		initImei();
	}

	/**
	 * 开启一个线程去获取IMEI
	 */
	private void initImei() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				if (isImeiGot()) {// 已经获取到该ID了,就不用再去获取了
					Log.d(TAG, "initImei is not null, no need to get IMEI "
							+ mIMEI);
					return;
				}

				mIMEI = IMEI_RESULT_GETTING;// 设置该值为获取中

				long beginTime = System.currentTimeMillis();
				Log.d(TAG, "initEncodeImei begin TelephonyManager.getDeviceId");
				TelephonyManager tm = (TelephonyManager) mContext
						.getSystemService(Service.TELEPHONY_SERVICE);
				String deviceId = tm.getDeviceId();

				if (isImeiGot()) {// 已经获取到该ID了
					Log.d(TAG, "sEncodeIMEI is not null ,no need to get value "
							+ mIMEI);
				} else {
					mIMEI = deviceId == null ? "test" : deviceId;// ID为空对象则返回一个test(自定义,通常不管获得一个什么字符串,最好都要进行加密)
					Log.d(TAG, "mIMEI get a value =" + mIMEI);
					// 通知所有的监听器,将所有结果都回调
					for (ImeiGetListener listener : mEncodeImeiListenerList) {
						listener.onGetIMEIOk(mIMEI);
					}
					mEncodeImeiListenerList.clear();
				}

				// 打印一下具体的耗时
				long deltaIMEITime = System.currentTimeMillis() - beginTime;
				Log.d(TAG, "deltaIMEITime=" + deltaIMEITime);
			}
		}).start();

	}

	/**
	 * 如果不是空对象,而且不是正在获取中,则说明已经获得了结果了
	 * 
	 * @return
	 */
	private boolean isImeiGot() {
		return mIMEI != null && !IMEI_RESULT_GETTING.equals(mIMEI);
	}

	/**
	 * 
	 * @return
	 */
	public String getImei() {
		// 空对象,则返回没有空值加密数据
		if (null == mIMEI) {
			Log.d(TAG, "getImei mIMEI==null");
			return getNullImei();
		}

		// 正在获取,则再开启一个线程去获取,
		// 返回IMEI_RESULT_GETTING,获取的地方需要判断,如果是该值则要做一些处理
		if (IMEI_RESULT_GETTING.equals(mIMEI) || "".equals(mIMEI)) {// getting
			Log.d(TAG, "getImei mIMEI is getting");
			initImei();
			return IMEI_RESULT_GETTING;
		}

		// 否则就是已经获取到了,直接返回所需要的
		Log.d(TAG, "getImei mIMEI=" + mIMEI);
		return mIMEI;
	}

	public String getNullImei() {
		return "test";// 自己处理加密
	}

	public boolean isImeiGetting(String encodeImei) {
		if (encodeImei.equals(IMEI_RESULT_GETTING)) {
			Log.d(TAG, "isImeiGetting=true");
			return true;
		}
		return false;
	}

	public void addOnImeiGetListener(ImeiGetListener encodeImeiListener) {
		if (encodeImeiListener == null) {
			return;
		}
		if (isImeiGot()) {// 已经获得了,无需再添加监听器
			encodeImeiListener.onGetIMEIOk(mIMEI);
			return;
		}
		if (!mEncodeImeiListenerList.contains(encodeImeiListener)) {// 已经有该监听器了
			mEncodeImeiListenerList.add(encodeImeiListener);
		}
	}

	// 监听,回调
	public interface ImeiGetListener {
		public void onGetIMEIOk(String imei);
	}
}