android开发中 PopupWindow 自适应宽度布局

 更新时间:2016年9月20日 19:56  点击:2038
PopupWindow在android.widget包下,弹出窗口的形式展示。PopupWindow弹出的菜单随内容的宽度自适应,重写ListView的onMeasure()方法是个不错的解决办法。

PopupWindow用法

使用PopupWindow可实现弹出窗口效果,,其实和AlertDialog一样,也是一种对话框,两者也经常混用,但是也各有特点。下面就看看使用方法。
首先初始化一个PopupWindow,指定窗口大小参数。

PopupWindow mPop = new PopupWindow(getLayoutInflater().inflate(R.layout.window, null),
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
也可以分开写:
LayoutInflater mLayoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
//自定义布局
ViewGroup menuView = (ViewGroup) mLayoutInflater.inflate(
                    R.layout.window, null, true);
PopupWindow mPop = new PopupWindow(menuView, LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT, true);
当然也可以手动设置PopupWindow大小。
mPop.setContentView(menuView );//设置包含视图
mPop.setWidth(int )
mPop.setHeight(int )//设置弹出框大小

设置进场动画:
mPop.setAnimationStyle(R.style.AnimationPreview);//设置动画样式


mPop.setOutsideTouchable(true);//这里设置显示PopuWindow之后在外面点击是否有效。如果为false的话,那么点击PopuWindow外面并不会关闭PopuWindow。当然这里很明显只能在Touchable下才能使用。

当有mPop.setFocusable(false);的时候,说明PopuWindow不能获得焦点,即使设置设置了背景不为空也不能点击外面消失,只能由dismiss()消失,但是外面的View的事件还是可以触发,back键也可以顺利dismiss掉。当设置为popuWindow.setFocusable(true);的时候,加上下面两行设置背景代码,点击外面和Back键才会消失。
mPop.setFocusable(true);
需要顺利让PopUpWindow dimiss(即点击PopuWindow之外的地方此或者back键PopuWindow会消失);PopUpWindow的背景不能为空。必须在popuWindow.showAsDropDown(v);或者其它的显示PopuWindow方法之前设置它的背景不为空:

mPop.setBackgroundDrawable(new ColorDrawable(0));



mPop.showAsDropDown(anchor, 0, 0);//设置显示PopupWindow的位置位于View的左下方,x,y表示坐标偏移量

mPop.showAtLocation(findViewById(R.id.parent), Gravity.LEFT, 0, -90);(以某个View为参考),表示弹出窗口以parent组件为参考,位于左侧,偏移-90。
mPop.setOnDismissListenerd(new PopupWindow.OnDismissListener(){})//设置窗口消失事件

注:window.xml为布局文件

总结:

1、为PopupWindow的view布局,通过LayoutInflator获取布局的view.如:

LayoutInflater inflater =(LayoutInflater)            

this.anchor.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View textEntryView =  inflater.inflate(R.layout.paopao_alert_dialog, null);

       

2、显示位置,可以有很多方式设置显示方式

pop.showAtLocation(findViewById(R.id.ll2), Gravity.LEFT, 0, -90);

或者

pop.showAsDropDown(View anchor, int xoff, int yoff)

 

3、进出场动画

pop.setAnimationStyle(R.style.PopupAnimation);

 

4、点击PopupWindow区域外部,PopupWindow消失

   this.window = new PopupWindow(anchor.getContext());

 

this.window.setTouchInterceptor(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

if(event.getAction() ==MotionEvent.ACTION_OUTSIDE) {              

BetterPopupWindow.this.window.dismiss();

return true;

}

return false;

}

});



PopupWindow 自适应宽度实例


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        
     int maxWidth = meathureWidthByChilds() + getPaddingLeft() + getPaddingRight(); super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth,MeasureSpec.EXACTLY),heightMeasureSpec);   
}   
   public int meathureWidthByChilds() {     
      int maxWidth = 0;    
      View view = null;
      for (int i = 0; i < getAdapter().getCount(); i++) {
           view = getAdapter().getView(i, view, this);     
           view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);     
      if (view.getMeasuredWidth() > maxWidth){        
         maxWidth = view.getMeasuredWidth();    
      }       
  }       
   return maxWidth;  
}



PopupWindow自适应布局

Android自带的Menu菜单,常常无法满足我们的需求,所以就只有自己写menu菜单,通常的选择是用PopupWindow来实现自定义的menu菜单,先看代码,再来说明要注意的几点:

    View menuView = inflater.inflate(R.layout.menu_popwindow, null);  
    final PopupWindow p = new PopupWindow(mContext);  
    p.setContentView(menuView);  
    p.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
    p.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);  
    p.setAnimationStyle(R.style.MenuWindow);  
    p.setOnDismissListener(this);  
    p.setOutsideTouchable(false);  
    p.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));  
    p.setFocusable(true); // 如果把焦点设置为false,则其他部份是可以点击的,也就是说传递事件时,不会先走PopupWindow  
      
    mPopwindow = p;  

来说明其中的几点:

  1. 为了让PopupWindow自适应屏幕的宽度,设置宽度时用ViewGroup.LayoutParams.FILL_PARENT,为了自适应子布局的高度,设置高度时用ViewGroup.LayoutParams.WRAP_CONTENT

  2. 由于PopupWindow类没有继承ViewGroup类,所以inflater.inflate(int resource, ViewGroup root)方法的第二个参数只能传为null,传null会使最外层布局的android:layout_xxx都不起作用。所以高度是以第二层布局为主

  3. 为了设置背景和边距,其背景只能设置在第二层布局里,因第一层布局的android:layout_marginXxx不起作用,而设置android:padding_Xxx不会影响背景。

  4. menu有一个特点,就是点外部,menu菜单要消失,要实现这个,有几个属性要一起设置:p.setOutsideTouchable(false);p.setBackgroundDrawable();p.setFocusable(true);

本文我们研究一下关于Android获取短信验证码的工作原理,由于短信验证码用来保护个人信息的安全性,在手机app应用非常广范,我们可以认真的学习一下。

短信验证需要服务器端生成一个验证码,然后发送到用户输入的手机上,这个过程需要服务器主动向客户发送验证短信,所以这是就需要一个移动或联通的发送短息接口,由于本人目前尚处于学生阶段,没有获得这个接口的权限,所以我就选择了借助网上的移动开发服务平台,来完成这个功能的实现,这里我借用的平台是:http://dashboard.mob.com/,大家可以关注一下,这个平台为我们开发移动应用提供了很好的技术指导,可以大大缩短我们的开发周期。废话不多说,下面开始我们今天的重点。

官方为我们提供了两种设计方式:第一种调用内部GUI实现;另一种通过自定义GUI实现,对于第一种方式,我就不再多讲,因为官方文档提供了很详细的实行步骤,大家只需要按照上面的步骤去实现即可,没有难度。本篇我将带领大家通过自定义GUI实现短信验证功能。首先开发之前你可以先查阅一下官方提供的无GUI  API,然后下载一下官方提供的dome,做好这些工作之后,我们就可以开始我们的设计了。

  1、将demo中的libs下的SMSSDK-1.1.5.jar和armeabi文件夹拷贝到我们项目的libs目录下,这是官方提供的类库jar包。

  2、在AndroidManifest.xml文件添加权限和声明Action:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android_sms"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
    
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.android_sms.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity
            android:name="cn.smssdk.SMSSDKUIShell"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:windowSoftInputMode="stateHidden|adjustResize" />
        
    </application>

</manifest>

  3、设计我们的布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:text="短信验证"
        android:textColor="#00ffaa"
        android:textSize="20dp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/textView2"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:text="手机号:" />

    <EditText
        android:id="@+id/phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textView1"
        android:layout_alignBottom="@+id/textView1"
        android:layout_toRightOf="@+id/textView1"
        android:maxLength="11"
        android:ems="11"
        android:inputType="phone" >
        <requestFocus />
        </EditText>

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_marginTop="40dp"
        android:layout_below="@+id/phone"
        android:text="验证码:"/>

    <EditText
        android:id="@+id/cord"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_alignBaseline="@+id/textView3"
        android:layout_alignBottom="@+id/textView3"
        android:layout_alignLeft="@+id/phone"
        android:ems="4"
        android:maxLength="4"
        android:inputType="phone" />

    <Button
        android:id="@+id/getcord"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/cord"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/cord"
        android:visibility="visible"
        android:text="获取验证码" />

    <Button
        android:id="@+id/savecord"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/cord"
        android:layout_margin="20dp"
        android:text="验证" />

    <TextView
        android:id="@+id/now"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/savecord"
        android:layout_toRightOf="@+id/cord"
        android:gravity="center_horizontal"
        android:visibility="gone"
        android:text="提示信息"
        android:textColor="#aaaaaa" />
    
</RelativeLayout>

  4、我们的MainActivity:

/**
 * 自定义GUI短信验证
 * @time: 2015年7月4日
 */
public class MainActivity extends Activity implements OnClickListener{
    
    private EditText phone;
    private EditText cord;
    private TextView now;
    private Button getCord;
    private Button saveCord;
    
    private String iPhone;
    private String iCord;
    private int time = 60;
    private boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        init();
        
     SMSSDK.initSDK(this, "<您的appkey>", "<您的appsecret>");
        EventHandler eh=new EventHandler(){

            @Override
            public void afterEvent(int event, int result, Object data) {
                
                Message msg = new Message();
                msg.arg1 = event;
                msg.arg2 = result;
                msg.obj = data;
                handler.sendMessage(msg);
            }
            
        };
        SMSSDK.registerEventHandler(eh);
        
    }

    private void init() {
        phone = (EditText) findViewById(R.id.phone);
        cord = (EditText) findViewById(R.id.cord);
        now = (TextView) findViewById(R.id.now);
        getCord = (Button) findViewById(R.id.getcord);
        saveCord = (Button) findViewById(R.id.savecord);
        getCord.setOnClickListener(this);
        saveCord.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.getcord:
            if(!TextUtils.isEmpty(phone.getText().toString().trim())){
                if(phone.getText().toString().trim().length()==11){
                    iPhone = phone.getText().toString().trim();
                    SMSSDK.getVerificationCode("86",iPhone);
                    cord.requestFocus();
                    getCord.setVisibility(View.GONE);
                }else{
                    Toast.makeText(MainActivity.this, "请输入完整电话号码", Toast.LENGTH_LONG).show();
                    phone.requestFocus();
                }
            }else{
                Toast.makeText(MainActivity.this, "请输入您的电话号码", Toast.LENGTH_LONG).show();
                phone.requestFocus();
            }
            break;

        case R.id.savecord:
            if(!TextUtils.isEmpty(cord.getText().toString().trim())){
                if(cord.getText().toString().trim().length()==4){
                    iCord = cord.getText().toString().trim();
                    SMSSDK.submitVerificationCode("86", iPhone, iCord);
                    flag = false;
                }else{
                    Toast.makeText(MainActivity.this, "请输入完整验证码", Toast.LENGTH_LONG).show();
                    cord.requestFocus();
                }
            }else{
                Toast.makeText(MainActivity.this, "请输入验证码", Toast.LENGTH_LONG).show();
                cord.requestFocus();
            }
            break;
            
        default:
            break;
        }
    }
    
    //验证码送成功后提示文字
    private void reminderText() {
        now.setVisibility(View.VISIBLE);
        handlerText.sendEmptyMessageDelayed(1, 1000);
    }

    Handler handlerText =new Handler(){
        public void handleMessage(Message msg) {
            if(msg.what==1){
                if(time>0){
                    now.setText("验证码已发送"+time+"秒");
                    time--;
                    handlerText.sendEmptyMessageDelayed(1, 1000);
                }else{
                    now.setText("提示信息");
                    time = 60;
                    now.setVisibility(View.GONE);
                    getCord.setVisibility(View.VISIBLE);
                }
            }else{
                cord.setText("");
                now.setText("提示信息");
                time = 60;
                now.setVisibility(View.GONE);
                getCord.setVisibility(View.VISIBLE);
            }
        };
    };
    
    Handler handler=new Handler(){

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            int event = msg.arg1;
            int result = msg.arg2;
            Object data = msg.obj;
            Log.e("event", "event="+event);
            if (result == SMSSDK.RESULT_COMPLETE) {
                //短信注册成功后,返回MainActivity,然后提示新好友
                if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {//提交验证码成功,验证通过
                    Toast.makeText(getApplicationContext(), "验证码校验成功", Toast.LENGTH_SHORT).show();
                    handlerText.sendEmptyMessage(2);
                } else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE){//服务器验证码发送成功
                    reminderText();
                    Toast.makeText(getApplicationContext(), "验证码已经发送", Toast.LENGTH_SHORT).show();
                }else if (event ==SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES){//返回支持发送验证码的国家列表
                    Toast.makeText(getApplicationContext(), "获取国家列表成功", Toast.LENGTH_SHORT).show();
                }
            } else {
                if(flag){
                    getCord.setVisibility(View.VISIBLE);
                    Toast.makeText(MainActivity.this, "验证码获取失败,请重新获取", Toast.LENGTH_SHORT).show();
                    phone.requestFocus();
                }else{
                    ((Throwable) data).printStackTrace();
                    int resId = getStringRes(MainActivity.this, "smssdk_network_error");
                    Toast.makeText(MainActivity.this, "验证码错误", Toast.LENGTH_SHORT).show();
                    cord.selectAll();
                    if (resId > 0) {
                        Toast.makeText(MainActivity.this, resId, Toast.LENGTH_SHORT).show();
                    }
                }
                
            }
            
        }
        
    };
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        SMSSDK.unregisterAllEventHandler();
    }

}

  注:appkey和appsecret:在http://dashboard.mob.com/注册一个账号后,创建一个发送短信的应用,系统会自动为生成appkey和appsecret

    handlerText是我自定义设计的Handker对象,用于当服务器发送验证码后,提醒用户注意。

  最后附图两张,供大家参考:





android获取短信验证码并自动填写的实现

现在的应用在注册登录或者修改密码中都用到了短信验证码,那在android中是如何实现获取短信验证码并自动填写的呢?

首先,需要要在manifest中注册接收和读取短信的权限:

    <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
    <uses-permission android:name="android.permission.READ_SMS"/>


实现一个广播SMSBroadcastReceiver来监听短信:

package com.example.receive;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;


/**
 * 短信监听
 * @author
 *
 */
public class SMSBroadcastReceiver extends BroadcastReceiver {
    
    private static MessageListener mMessageListener;
    public static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
    
    public SMSBroadcastReceiver() {
        super();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(SMS_RECEIVED_ACTION)) {
                Object[] pdus = (Object[]) intent.getExtras().get("pdus");
                for(Object pdu:pdus) {
                    SmsMessage smsMessage = SmsMessage.createFromPdu((byte [])pdu);
                    String sender = smsMessage.getDisplayOriginatingAddress();
                    //短信内容
                    String content = smsMessage.getDisplayMessageBody();
                    long date = smsMessage.getTimestampMillis();
                    Date tiemDate = new Date(date);
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String time = simpleDateFormat.format(tiemDate);

                    //过滤不需要读取的短信的发送号码
                    if ("+8613450214963".equals(sender)) {
                        mMessageListener.onReceived(content);
                        abortBroadcast();
                    }
                }
            }
        
    }
    
    //回调接口
    public interface MessageListener {
        public void onReceived(String message);
    }
    
    public void setOnReceivedMessageListener(MessageListener messageListener) {
        this.mMessageListener = messageListener;
    }
}


在需要填写验证码的Activity中,生产SMSBroadcastReceiver的实例,实现onReceived的回调接口。为了节约系统资源,我们使用动态注册注销广播的方法。

package com.example.smstest;

import com.example.receive.SMSBroadcastReceiver;

import android.os.Bundle;
import android.app.Activity;
import android.content.IntentFilter;
import android.view.Menu;
import android.widget.EditText;

public class MainActivity extends Activity {
    
    private EditText edtPassword;
    private SMSBroadcastReceiver mSMSBroadcastReceiver;
    
    private static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        edtPassword = (EditText) findViewById(R.id.password);
    }
    
    @Override
    protected void onStart() {
        super.onStart();
        //生成广播处理
        mSMSBroadcastReceiver = new SMSBroadcastReceiver();

        //实例化过滤器并设置要过滤的广播
        IntentFilter intentFilter = new IntentFilter(ACTION);
        intentFilter.setPriority(Integer.MAX_VALUE);
        //注册广播
        this.registerReceiver(mSMSBroadcastReceiver, intentFilter);

        mSMSBroadcastReceiver.setOnReceivedMessageListener(new SMSBroadcastReceiver.MessageListener() {
            @Override
            public void onReceived(String message) {

                edtPassword.setText(message);

            }
        });
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
         //注销短信监听广播
        this.unregisterReceiver(mSMSBroadcastReceiver);
    }


}

Genymotion是一套完整的工具,它提供了Android虚拟环境。它简直就是开发者、测试人员、推销者甚至是游戏玩家的福音。而且Genymotion支持Windows、Linux和Mac OS等操作系统,容易安装和使用,本文就来看看 Genymotion 安装使用图解教程.

Genymotion模拟器提供了Android虚拟环境,为安卓开发测试人员带来了极大的便利。Genymotion的启动速度比Eclipse自带的模拟器要快得多,运行起来非常的流畅,堪称Eclipse拥趸的调试神器!

Genymotion兼容的操作系统有:Microsoft Windows 32/64 bits、Mac OSX 10.5+ 、 Linux 32/64 bits

Genymotion官网:https://www.genymotion.com 


Android Studio本身的模拟器已经很强大,所以Genymotion还是配合Eclipse使用比较多一点,下面也是基于Eclipse编辑器进行讲解。

1、翻墙

Genymotion的使用需要注册账户并登录,但如果没有翻墙的话,注册和登录都是很难操作成功的。

在这里介绍个VPN,可以免费使用一段时间,足够安装完环境了。

Green VPN官网:https://www.igreenjsq.co

在官网注册一个账户,打开注册时填写的邮箱激活一下账户。下载PC客户端,登录账号,直接点击“链接”按钮:


VPN连接成功了,但对于一个程序员来说,上个技术类的网站都要翻墙,简直是一种耻辱!

2、注册账号并下载安装Genymotion

Genymotion基于VirtualBox虚拟机才能运行,在下载Genymotion时应选择相应的版本(软件下载不需要翻墙):


“Get Genymotion (without VirtualBox) (24.03MB)”版本适合于已经安装了VirtualBox的电脑,“Get Genymotion (126.02MB)”版本适合于尚未安装VirtualBox的电脑,安装该版本完成的时候会提示是否安装VirtualBox环境,点击“是”按钮,就顺带的把VirtualBox也安装了:


3、添加虚拟机

运行Genymotion,软件会提示是否添加一个虚拟设备:


点击“Yes”,弹出登录界面:


动不动就要登录,这也是我非常想吐槽的一个地方!

如果安装了翻墙软件,登录是没问题的;没有安装翻墙软件的话,也可以在网上找一个代理IP,设置一下:


登录成功后会出现一个设备列表:


选择需要用到的虚拟设备进行安装,120MB左右一个,下载过程可以不开VPN。

4、Eclipse安装Genymotion插件


Name:Genymobile Location:http://plugins.genymotion.com/eclipse/ ,点击“OK”


选择并下载(记得开VPN):


安装完成,重新启动Eclipse,可以看到重启后软件界面多了一个小图标:


点击图标,提示需要配置Genymotion安装的目录:


点击“OK”,进入配置界面:


点击“OK”进入虚拟设备选择界面,选择相应的虚拟设备,并启动:


Genymotion启动成功:


运行一个测试程序:


程序已经成功的在Genymotion模拟器中运行了:


PS:关闭Genymotion运行程序的话,默认启动的是Eclipse自带的模拟器。

现在,我们的Genymotion模拟器安装就告一段落了。


本文我们先来看看android如何通过子线程来实现动画的实例,然后我们再详细讲讲Android 动画实现的原理,这样才能深入浅出。

android通过子线程来实现动画的实例


Android动画,一般是相对原始位置进行参照,本文我们来看看通过子线程修改物体位置实现动画的实例。


布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/show"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="40dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/button1"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="行动" />

    <Button
        android:id="@+id/button2"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="28dp"
        android:layout_toLeftOf="@+id/button1"
        android:text="获取位置" />

</RelativeLayout>

动画代码:

public class MainActivity extends Activity {

    TextView textView;
    MyRuns myRuns;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.show);

        
        myRuns=new MyRuns(new MyHead(textView, 4, 8), true);//位移动画
    }

    static class MyHead extends Handler {// 坐标动画

        View view;// 操作元素
        float cx;
        float cy;

        public MyHead(View view, float cx, float cy) {
            super();
            this.view = view;
            this.cx = cx;
            this.cy = cy;
        }

        @Override
        public void handleMessage(Message msg) {
            // 更新ui
            view.setX(view.getX() + cx);
            view.setY(view.getY() + cy);
            super.handleMessage(msg);
        }

    }

    // 子线程更新位置
    class MyRuns implements Runnable {//更新UI界面

        MyHead head;
        boolean isFire = false;

        public MyRuns(MyHead head, boolean isFire) {
            super();
            this.head = head;
            this.isFire = isFire;
        }

        public boolean isFire() {
            return isFire;
        }

        public void setFire(boolean isFire) {
            this.isFire = isFire;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while (true) {
                    if (!isFire) {
                        break;//停止动画
                    }
                    Thread.sleep(80);
                    Message message = new Message();
                    message.what = 3;
                    message.obj = "";
                    head.sendMessage(message);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }

    }
    
    //开始运动
    void StartThreed(MyRuns myRuns){
        myRuns.setFire(true);//开启
        new Thread(myRuns).start();
    }

    public void MyCLick(View view) {
        if (view.getId() == R.id.button1) {
            StartThreed(myRuns);
        } else if (view.getId() == R.id.button2) {
            myRuns.setFire(false);//结束子线程
            Toast.makeText(getApplicationContext(),
                    "坐标" + textView.getX() + "||" + textView.getY(),
                    Toast.LENGTH_SHORT).show();
        }

    }

}




Android 动画原理

Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果。Android 动画框架详解由原理篇和实例篇两部分组成。本文是第一部分原理篇,主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。第二部分实例篇将在原理篇的基础上,向您展示一个动画实例的实现。

 Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述 Android 的动画框架是如何实现的。 任何一个框架都有其优势和局限性,只有明白了其实现原理,开发者才能知道哪些功能可以利用框架来实现,哪些功能须用其他途径实现。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换 ( 平移、缩放、旋转 ) 产生动画效果;第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似。本文是由两部分组成的有关 Android 动画框架详解的第一部分原理篇, 主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。我们先看一下动画示例来一点感性认识。

Android 动画使用示例

使用动画示例程序的效果是点击按钮,TextView 旋转一周。读者也可以参看 Apidemos 中包 com.example.android.apis.animationview 下面的 Transition3d 和 com.example.android.apis.view 下面的 Animation1/Animation2/Animation3 示例代码。

清单 1. 代码直接使用动画

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.AccelerateDecelerateInterpolator;   
import android.view.animation.Animation;   
import android.view.animation.RotateAnimation;   
import android.widget.Button;   
 
public class TestAnimation extends Activity implements OnClickListener  
{   
    public void onCreate(Bundle savedInstanceState)  
    {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);   
        Button btn =(Button)findViewById(R.id.Button);   
        btn.setOnClickListener(this);   
    }  
 
    public void onClick(View v)  
    {   
        Animation anim=null;   
        anim=new?RotateAnimation(0.0f,+360.0f);   
       anim.setInterpolator(new AccelerateDecelerateInterpolator());   
       anim.setDuration(3000);   
       findViewById(R.id.TextView01).startAnimation(anim);   
    }   
}

 使用 XML 文件方式,在打开 Eclipse 中新建的 Android 工程的 res 目录中新建 anim 文件夹,然后在 anim 目录中新建一个 myanim.xml( 注意文件名小写 ),内容如下 :


图 1. 使用 xml 文件方式

 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android">  
 <rotate   
    android:interpolator="@android:anim/acclerate_decelerate_interpolator"  
    android:formDegress="0"  
    android:toDegress="+360"  
    android:duration="3000" />  
      
<!--rotate 旋转动画效果  
    属性:  
   interpolator 指定一个动画的插入器,用来控制动画的速度变化  
   fromDegress  动画起始时物件的角度  
   toDegress    动画结束时物件的旋转角度,正代表顺时针  
   duration     动画的持续时间,以毫秒为单位-->  
 
/set>

其中的 java 代码如下:

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.Animation;   
import android.view.animation.AnimationUtils;   
import android.widget.Button;   
import android.widget.TextView;   
public class TestAnimation extends Activity implements OnClickListener  
{   
   public void onCreate(Bundle savedInstanceState)  
   {   
       super.onCreate(savedInstanceState);   
       setContentView(R.layout.main);   
       Button btn =(Button)findViewById(R.id.Button01);   
       btn.setOnClickListener(this);   
   }   
 
   @Override   
   public void onClick(View v)  
   {   
      Animation anim=AnimationUtils.loadAnimation(this,R.anim.my_rotate_action);   
    findViewById(R.id.TextView01).startAnimation(anim);   
   }   
}


Android 动画框架原理

现有的 Android 动画框架是建立在 View 的级别上的,在 View 类中有一个接口 startAnimation 来使动画开始,startAnimation 函数会将一个 Animation 类别的参数传给 View,这个 Animation 是用来指定我们使用的是哪种动画,现有的动画有平移,缩放,旋转以及 alpha 变换等。如果需要更复杂的效果,我们还可以将这些动画组合起来,这些在下面会讨论到。

要了解 Android 动画是如何画出来的,我们首先要了解 Android 的 View 是如何组织在一起,以及他们是如何画自己的内容的。每一个窗口就是一棵 View 树,下面以我们写的 android_tabwidget_tutorial.doc 中的 tab 控件的窗口为例,通过 android 工具 hierarchyviewer 得到的窗口 View Tree 如下图 1 所示:

图 2. 界面 View 结构图


界面 View 结构图

图 3. 界面 View 结构和显示对应图


界面 View 结构和显示对应图图

其实这个图不是完整的,没有把 RootView 和 DecorView 画出来,RootView 只有一个孩子就是 DecorView,这里整个 View Tree 都是 DecorView 的子 View,它们是从 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 这个 layout 文件 infalte 出来的,感兴趣的读者可以参看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函数部分的代码。我们可以修改布局文件和代码来做一些比较 cool 的事情,如象 Windows 的缩小 / 关闭按钮等。标题窗口以下部分的 FrameLayou 就是为了让程序员通过 setContentView 来设置用户需要的窗口内容。因为整个 View 的布局就是一棵树,所以绘制的时候也是按照树形结构遍历来让每个 View 进行绘制。ViewRoot.java 中的 draw 函数准备好 Canvas 后会调用 mView.draw(canvas),其中 mView 就是调用 ViewRoot.setView 时设置的 DecorView。然后看一下 View.java 中的 draw 函数:

递归的绘制整个窗口需要按顺序执行以下几个步骤:

绘制背景;

如果需要,保存画布(canvas)的层为淡入或淡出做准备;

绘制 View 本身的内容,通过调用 View.onDraw(canvas) 函数实现,通过这个我们应该能看出来 onDraw 函数重载的重要性,onDraw 函数中绘制线条 / 圆 / 文字等功能会调用 Canvas 中对应的功能。下面我们会 drawLine 函数为例进行说明;

绘制自己的孩子(通常也是一个 view 系统),通过 dispatchDraw(canvas) 实现,参看 ViewGroup.Java 中的代码可知,dispatchDraw->drawChild->child.draw(canvas) 这样的调用过程被用来保证每个子 View 的 draw 函数都被调用,通过这种递归调用从而让整个 View 树中的所有 View 的内容都得到绘制。在调用每个子 View 的 draw 函数之前,需要绘制的 View 的绘制位置是在 Canvas 通过 translate 函数调用来进行切换的,窗口中的所有 View 是共用一个 Canvas 对象

如果需要,绘制淡入淡出相关的内容并恢复保存的画布所在的层(layer)

绘制修饰的内容(例如滚动条),这个可知要实现滚动条效果并不需要 ScrollView,可以在 View 中完成的,不过有一些小技巧,具体实现可以参看我们的 TextViewExample 示例代码

当一个 ChildView 要重画时,它会调用其成员函数 invalidate() 函数将通知其 ParentView 这个 ChildView 要重画,这个过程一直向上遍历到 ViewRoot,当 ViewRoot 收到这个通知后就会调用上面提到的 ViewRoot 中的 draw 函数从而完成绘制。View::onDraw() 有一个画布参数 Canvas, 画布顾名思义就是画东西的地方,Android 会为每一个 View 设置好画布,View 就可以调用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去画内容。每一个 ChildView 的画布是由其 ParentView 设置的,ParentView 根据 ChildView 在其内部的布局来调整 Canvas,其中画布的属性之一就是定义和 ChildView 相关的坐标系,默认是横轴为 X 轴,从左至右,值逐渐增大,竖轴为 Y 轴,从上至下,值逐渐增大 , 见下图 :

图 4. 窗口坐标系


窗口坐标系

Android 动画就是通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的,下面以平移动画来做示例,见下图 4,假设在动画开始时 ChildView 在 ParentView 中的初始位置在 (100,200) 处,这时 ParentView 会根据这个坐标来设置 ChildView 的画布,在 ParentView 的 dispatchDraw 中它发现 ChildView 有一个平移动画,而且当前的平移位置是 (100, 200),于是它通过调用画布的函数 traslate(100, 200) 来告诉 ChildView 在这个位置开始画,这就是动画的第一帧。如果 ParentView 发现 ChildView 有动画,就会不断的调用 invalidate() 这个函数,这样就会导致自己会不断的重画,就会不断的调用 dispatchDraw 这个函数,这样就产生了动画的后续帧,当再次进入 dispatchDraw 时,ParentView 根据平移动画产生出第二帧的平移位置 (500, 200),然后继续执行上述操作,然后产生第三帧,第四帧,直到动画播完。具体算法描述如清单 2:


清单 2. 算法

 

dispatchDraw()   
    {   
        ....   
        Animation a = ChildView.getAnimation()   
        Transformation tm = a.getTransformation();   
        Use tm to set ChildView's Canvas;   
        Invalidate();   
        ....   
    }  


 图 5. 平移动画示意图


平移动画示意图

以上是以平移动画为例子来说明动画的产生过程,这其中又涉及到两个重要的类型,Animation 和 Transformation,这两个类是实现动画的主要的类,Animation 中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),以上面的平移矩阵为例子,当调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation,这个 Transformation 中的矩阵如下:

图 6. 矩阵变换图


矩阵变换图

所以具体的动画只需要重载 applyTransformation 这个函数即可,类层次图如下:

图 7. 动画类继承关系图


动画类继承关系图

用户可以定义自己的动画类,只需要继承 Animation 类,然后重载 applyTransformation 这个函数。对动画来说其行为主要靠差值点来决定的,比如,我们想开始动画是逐渐加快的或者逐渐变慢的,或者先快后慢的,或者是匀速的,这些功能的实现主要是靠差值函数来实现的,Android 提供了 一个 Interpolator 的基类,你要实现什么样的速度可以重载其函数 getInterpolation,在 Animation 的 getTransformation 中生成差值点时,会用到这个函数。

从上面的动画机制的分析可知某一个 View 的动画的绘制并不是由他自己完成的而是由它的父 view 完成,所有我们要注意上面 TextView 旋转一周的动画示例程序中动画的效果并不是由 TextView 来绘制的,而是由它的父 View 来做的。findViewById(R.id.TextView01).startAnimation(anim) 这个代码其实是给这个 TextView 设置了一个 animation,而不是进行实际的动画绘制,代码如下 :

 public void startAnimation(Animation animation)   
    {   
      
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);   
      
        setAnimation(animation); invalidate();   
      
    } 

 

其他的动画机制的代码感兴趣的读者请自己阅读,希望通过原理的讲解以后看起来会轻松点,呵呵。

以上就是 Android 的动画框架的原理,了解了原理对我们的开发来说就可以清晰的把握动画的每一帧是怎样生成的,这样便于开发和调试。它把动画的播放 / 绘制交给父 View 去处理而不是让子 View 本身去绘制,这种从更高的层次上去控制的方式便于把动画机制做成一个易用的框架,如果用户要在某个 view 中使用动画,只需要在 xml 描述文件或代码中指定就可以了,从而把动画的实现和 View 本身内容的绘制(象 TextView 里面的文字显示)分离开了,起到了减少耦合和提高易用性的效果。

动画实现示例

在这个例子中,将要实现一个绕 Y 轴旋转的动画,这样可以看到 3D 透视投影的效果,代码如下 ( 清单 4):

清单 3. 实现一个绕 Y 轴旋转的动画


 package com.example.android.apis.animation;   
import android.view.animation.Animation;   
import android.view.animation.Transformation;   
import android.graphics.Camera;   
import android.graphics.Matrix;   
/**  
* An animation that rotates the view on the Y axis between two specified angles.  
* This animation also adds a translation on the Z axis (depth) to improve the effect.  
*/   
public class Rotate3dAnimation extends Animation   
{   
   private final float mFromDegrees;   
   private final float mToDegrees;   
   private final float mCenterX;   
   private final float mCenterY;   
   private final float mDepthZ;   
   private final boolean mReverse;   
   private Camera mCamera;   
   /**  
    * Creates a new 3D rotation on the Y axis. The rotation is defined by its  
    * start angle and its end angle. Both angles are in degrees. The rotation  
    * is performed around a center point on the 2D space, definied by a pair  
    * of X and Y coordinates, called centerX and centerY. When the animation  
    * starts, a translation on the Z axis (depth) is performed. The length  
    * of the translation can be specified, as well as whether the translation  
    * should be reversed in time.  
    *  
    * @param fromDegrees the start angle of the 3D rotation  
    * @param toDegrees the end angle of the 3D rotation  
    * @param centerX the X center of the 3D rotation  
    * @param centerY the Y center of the 3D rotation  
    * @param reverse true if the translation should be reversed, false otherwise  
    */   
   public Rotate3dAnimation(float fromDegrees, float toDegrees,   
                 float centerX,       float centerY,  
                 float depthZ,       boolean reverse)   
   {   
       mFromDegrees = fromDegrees;   
       mToDegrees = toDegrees;   
       mCenterX = centerX;   
       mCenterY = centerY;   
       mDepthZ = depthZ;   
       mReverse = reverse;   
   }   
 
   @Override   
   public void initialize(int width, int height, int parentWidth, int parentHeight)   
   {   
       super.initialize(width, height, parentWidth, parentHeight);   
       mCamera = new Camera();   
   }   
 
   @Override   
   protected void applyTransformation(float interpolatedTime, Transformation t)  
   {   
       final float fromDegrees = mFromDegrees;   
       float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);   
       final float centerX = mCenterX;   
       final float centerY = mCenterY;   
       final Camera camera = mCamera;   
       final Matrix matrix = t.getMatrix();   
       camera.save();   
         
       if (mReverse)  
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);   
       }   
       else   
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));   
       }   
         
       camera.rotateY(degrees);   
       camera.getMatrix(matrix);   
       camera.restore();   
       matrix.preTranslate(-centerX, -centerY);   
       matrix.postTranslate(centerX, centerY);   
   }   
}


在这个例子中我们重载了 applyTransformation 函数,interpolatedTime 就是 getTransformation 函 数传下来的差值点,在这里做了一个线性插值算法来生成中间角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); Camera 类是用来实现绕 Y 轴旋转后透视投影的,我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,当 ParentView 去为 ChildView 设置画布时,就会用它来设置坐标系,这样 ChildView 画出来的效果就是一个绕 Y 轴旋转同时带有透视投影的效果。利用这个动画便可以作出像立体翻页等比较酷的效果。如何使用这个 animation 请见 ApiDemos 程序包 com.example.android.apis.animation 中的 Transition3d.java 代码。


Android 中显示 Gif 格式图

有关这一部分,本文将不做详细介绍。 感兴趣的读者请参看 Apidemos 中 com.example.android.apis.graphics 下面的 BitmapDecode.java 中的示例代码。

这里先简单说明一下,它的实现是通过 Movie 这个类来对 Gif 文件进行读取和解码的,同时在 onDraw 函数中不断的绘制每一帧图片完成的,这个示例代码在 onDraw 中调用 invalidate 来反复让 View 失效来让系统不断调用 SampleView 的 onDraw 函数;至于选出哪一帧图片进行绘制则是传入系统当前时间给 Movie 类,然后让它根据时间顺序来选出帧图片。反复让 View 失效的方式比较耗资源,绘制效果允许的话可以采取延时让 View 失效的方式来减小 CPU 消耗。

目前使用这个方式播放一些 Gif 格式的动画时会出现花屏的现象,这是因为 Android 中使用的 libgif 库是比较老的版本,新的 tag 不支持,所以导致花屏,解决办法有制作 Gif 图片时别使用太新的 tag 或完善 android 中对应的 libgif 库。


结束语

本文介绍了 Android 动画框架的基本原理,可以帮助开发者深入理解 Android 的动画是如何实现的,从而能够充分利用 android 现有框架来做出够眩、够酷的动画效果。


本文主要讲的内容是Android5.0L因SystemUI ANR导致的黑屏的问题现象、解决方案、初步分析、深入分析问题、及相关问题等,如果你在做Android开发的时候也遇到这种现象,可以参考一下本文。

本文我们来讲讲Android5.0L因SystemUI ANR导致的黑屏问题分析


一、问题现象

用户直观看到的现象是黑屏。出问题时StatusBar、NavigationBar和墙纸消失。大部分发生在FOTA重启之后,出现概率很低。

Platform:MSM8916

Android版本:5.0.2L

BuildType:user

系统软件版本:VA6V+L5V0

系统RAM:1GB

参考机行为:

1、5.0L的Nexus4和5.1L的Nexus5都没有重现此问题。

二、解决方案

通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因:

1、开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭

2、在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出

3、这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回, 执行的方式是多线程异步处理

在当前代码的执行状态下有一定概率(很小,只有开机或者重启时走这个流程)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

针对以上问题的根本原因,我们给出以下解决方案:

1、修正代码的处理顺序

Closecamera 时先执行m_thermalAdapter.deinit等待thermal deamon线程将结果处理完成退出并返回,再free all pending api results,因为m_thermalAdapter.deinit会依赖pending api results,这同样是遵循初始化和反初始化的栈原则,即opencamera时最后初始化的是依赖别人最多的,但是不被别人依赖,因此 closecamera时需要先反初始化在opencamera时最后初始化的,按照栈的方式原则处理。

2、方案相关的具体代码和log





以上是发生死锁时锁对应的log以及相应代码和调用栈,通过红线圈住部分我们可以看到发生问题时的关键调用关系和状态,同时也给出了代码处理顺序有问题的地方。

3、最终方案的代码修改


三、问题初步分析

以Idol347出问题时候的一份典型trace和log为例,发现出问题时SystemUI的主线程block在了一个向CameraService发起的Binder调用中,从而导致SystemUI

的后续事件TimeOut引起ANR,主线程的具体trace如下:


然后继续追踪CameraService的服务端的trace,发现/system/bin/mediaserver中的处理CameraService的 Binder线程也被block了,然后追踪trace中各个线程的调用栈和互斥锁的使用,发现处理调用CameraService的 addlistener的Binderthread之所以被阻塞,是因为另外一个binder thread先占用了锁,然后在占用的过程中去注册thermal回调,但是注册的过程需要占用另外一个锁,但是这个锁被第三个thread在注销thermal回调的时候先占用,并且join另外一个thread,因此整个依赖环需要另外一个thread退出才能解,从当前的问题现象来看,这个thread不会太快退出,所以导致了ANR和黑屏问题。

通过初步分析我们发现的问题:

是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?


具体的调用栈和代码中锁的关系如下:











四、深入分析问题

经过初步我们定位到了第一个问题点,同时也产生了1个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。

1、是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?

通过进一步分析和查看代码发现,Join的另外一个thread不能很快退出是因为它在执行callback时等待另外一个条件的满足,具体逻辑调用关系如下:





另外一个条件之所以不满足的原因:

开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭,在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出,但是这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回,由于是多线程的异步处理,在当前代码的执行状态下就有一定概率(很小)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。

一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

五、其他相关问题

为什么大部分发生在FOTA升级之后?

由于这个问题是发生在启动或者重启时,而且只有这个过程才会有很小概率触发。而FOTA之后会自动重启,所以就有概率触发这个问题,由于用户平时使用中很少重启,所以概率不高。


[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • C#窗体布局方式详解

    这篇文章主要介绍了C#窗体布局方式详解的相关资料,需要的朋友可以参考下...2020-06-25
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Element图表初始大小及窗口自适应实现

    这篇文章主要介绍了Element图表初始大小及窗口自适应实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-10
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
  • Android开发中布局中的onClick简单完成多控件时的监听的利与弊

    本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20