-
业务无关公用代码 activity BaseActivity,公用逻辑 net 网络层 cache 缓存数据,图片相关处理 ui 自定义控件 utils(SharedPreferences)
-
项目包模块 activity,adapter,entity db SQLLite 相关逻辑的封装 engine 业务相关类 interfaces 接口,命名以I作为开头 listener 基于Listener接口,命名以On作为开头 UIL,Fresco 图片加载
-
Activity新的生命周期,统一事件编程模型 solid原则,单一职责,一个类,一个方法只做一件事 BaseActivity 子类实现它的抽象方法 Button setOnClickListener,提取出私有方法
-
实体化编程FastJson,GSON 有符号Annotation实体属性,泛型属性,使用崩溃 需要在混淆文件添加 -keepattributes Signature //避免混淆泛型 -keepattributes Annotation //不混淆注解
-
类型安全转换函数 convertInt(Object value,int default); 转换失败,返回默认值 ,其他long,String类型同理 substring(),长度不够,崩溃。判断是否越界,ex: length>1
抛弃AsyncTask,自定义网络底层封装框架
设计一套App缓存策略
设计一套MockService,模拟网络返回数据
封装用户Cookie逻辑
- JSON返回Response公用属性,可封装一个基类
- 网络底层封装在AndroidLib库中
使用原生ThreadPoolExecutor+Runnable+Handler
- onFail统一处理
- UrlConfigManager优化
- 不是每个请求都需要回调的
- ProgressBar 的处理
保存在SD卡,数据库中
POST请求,GET请求,请求URL作为键值,排序算法对URL中的key进行排序,检查是否有缓存数据,没有直接获取.
如果有缓存数据,与缓存数据的Expired,Version比较是否需要更新
- url.xml 中配置Node节点MockClass 属性,指定使用Mock子类生成的数据
MockClass="con.xxx.xxx.MockWeatherInfo"
- 使用反射工厂设计MockService,MockService是基类,有抽象方法,getJsonData(),返回手动生成的数据
public abstract class MockService{
public abstract String getJsonData();
}
- 实现反射机制
- 需要登录的操作,跳转到登录,然后回调
- 接口请求设置Cookie机制,获取网络连接 头部中的Set-Cookie,保存到CookieManager
- 防止刷屏,同一IP访问,设置短时间内请求次数,输入验证码等操作
- 防止重定向,判读域名Host
- gzip压缩
- 设置服务端客户端时间 ,时区造成的时间差
- 连接网络后对响应状态的判断
- 设置网络请求通用头部信息
- Android 浅析 HttpURLConnection
- 通信层面优化
- 接口返回数据进行gzip压缩,大于1KB才进行压缩,否则得不偿失
- 通常数据传输遵行JSON,推荐新的传输协议,ProtoBuf,这种协议是二进制的,表示大数据时,空间比JSON小很多
Protocol Buffers 及Nano-Proto-Buffers Protocol Buffers是Google设计的语言无关、平台无关的一种轻便高效的序列化结构数据存数格式,类似XML,但更小、更快、更简单、很适合做数据存储或者RPC数据交换的格式。它可用于通讯协议,数据存储等领域的与语言无关、平台无关,可扩展的序列化结构数据格式。 在移动端应该使用Nano-Proto-Buffers版本。因为普通的Protocol Buffers会生成非常冗长的代码,可能会增加APP内存占用,导致APK体积增长、性能下降,不注意的话,会很快遇到64K方法数限制问题。 - 解决频繁调用API问题
- HTTP协议速度远不如TCP协议,后者是长连接,可以使用TCP提高访问速度,一台服务器支持的长连接个数不多,需要更多服务器集成
- 建立离开页面取消网络请求机制
- 增加重试机制,如果Mobile API 是严格RESTful风格,将获取数据的接口定义为GET,操作数据的接口定义为POST.这样就可以为所有GET请求配置重试机制,对POST请求增加防止用户1分钟内频繁发起相同请求的机制
在APP启动时候告诉所有API接口重试次数
- 图片策略优化
- 图片URL加上 ?width=100&height=50
- 图片单独准备服务器,https://www.imagesever.com/getImage?param=(encode_value) encode_value是对图片url,width,height进行encode,服务器进行解密,对图片进行重新绘制
增加imageType(1-> 等比缩放后裁剪多余部分;2-> 等比缩放后不足的宽或者高填充白色),有缺点,频繁读写硬盘
规定宽高,如果尺寸有出入,获取面积最接近的 s=(w1-w)^2+(h1-h)^2 - 低速网络下,降低图片质量,请求URL增加quality参数,节省流量
- 极速模式 发现当前网络为2G,3G或4G当前模式是正常模式,提示是否要进入极速模式以节省流量. 如果是WIFI网络,当前模式是极速模式,提示用户是否切换回正常模式,在设置页面也要提供这个开关,用户手动切换模式
- cityId,cityName,pinyin,jianpin(全拼,简拼做字母排序和检索) 数据在不同平台的统一,便于维护,保存在本地,API获取城市列表,打开gzip压缩,增加版本号控制,及时更新,插入新的数据
- 增量更新机制(包括增 A,删 D ,改 E) 增加一个字段,type 判断数据是哪种情况,分别处理
- 基本HTML,JavaScript语法,PC 服务器搭建IIS. 在Assets内置.html页面,现实中是在远程服务器上,定好协议,APP调用JS的方法名称
<script type="text/javascript">
function changeColor(color){
doucument.body.style.backgroundColor=color;
}
</script>
wv.getSettings().setJavaScriptEnabled(true);
wv.loadUrl("file:https:// /android_asset/104.html");
btn.setOnClickListener(...){
...
String color="#00eeff";
wv.loadUrl("javascript:changeColor('"+color+"');");
}
- HTML5页面操作APP方法
<a onclick="baobao.callAndroidMethod(100,100,'ccc',true)">CallAndroidMethod</a>
新建JSInterface1类,包括callAndroidMethod方法的实现
class JSInterface1{
public void callAndroidMethod(int a,float b,String c,boolean d){
if(d){
String strMsg="-"+(a+1)+"-"+(b+1)+"-"+c+"-"+d;
new AlertDialog.Builder(this).setTitle("sss").setMessage(strMsg).show();
}
}
}
注册baobao和JSInterface1对应关系
wv.addJavaScriptInterface(new JSInterface1(),"baobao");
在方法前加@JavascriptInterface,否则不能触发JavaScript方法
- APP,HTML之间定义跳转协议,实现HTML5活动页面,路由设置
- GlobalVariables implements Parcelable,Cloneable
- 序列化本地文件缺点
- 每次都要重新执行序列化操作
- 序列化文件会因内存不够数据丢失
- Android 提供的数据类型并不全部支持序列化(支持:Y;不支持:N)
类型 | 是否支持 |
---|---|
简单类型int,String,Boolean | Y |
int[],int[][], String[],String[][],Boolean[] | Y |
ArrayList | Y |
Calendar | Y |
JSONObject | N |
JSONArray | N |
- Java类文件
- Activity:PersonActivity
- Adapter:PersonAdapter
- Entity/Bean:PersonEntity/PersonBean
- 资源文件
- act_person_addcustomer.xml
- item_lv_userlist.xml
- 一个页面使用资源,以页面名称做前缀
- 一个模块下多个页面使用,以模块为前缀
- 各个模块通用,common 作为前缀
- strings.xml 分模块 strings_module_a.xml
- 编码规范
- font_size_s ,font_size_l ,font_size_xxl ,font_color_red,person_btnLogin_text
- offset_2dp,offset_4dp
- 使用style
- 不要内部嵌套类
- 不同模块实体类不共用
- 安全数据类型转换
- 尽量使用ApplicationContext代替Context
- 使用常量代替枚举
- 复杂数据保存到本地
- CheckStyle
- CrashHandler implements UncaughtExceptionHandler
- 异常数据表结构
字段 | 描述 |
---|---|
id | 自增id |
client_type | Crash所在app |
page_name | Crash所在Activity |
exception_name | Crash所在Activity |
exception_stack | Crash详细信息 |
crash_type | 1 崩溃,0 try-catch |
app_version | app 版本 |
os_version | Android 系统版本 |
device_model | Android 手机型号 |
device_id | Android 手机设备号 |
network_type | 网络类型 |
channel_id | 渠道号 |
client_type | Android/iPhone |
memory_info | 内存使用情况 |
crash_time | 发生时间,数据库自动生成 |
- 统计,去重(数字,行号,相同信息,页面,截取前面的info),新增规则,去除昨天重复,新增今日不同Crash,按照pageOwner 分配给不同的人,建表,执行SQL脚本,C#程序,做成自动化执行脚本 归纳详细信息,即时性,查询Crash,趋势图
- 分析
- Android系统碎片化
- MobileAPI 返回了脏数据
- 混淆Keep,找不到类或方法Crash
- unknown source:执行javac时丢失了文件名和行号
<javac debug="true" debuglevel="source,lines".../>
- unknown source:执行混淆时时丢失了文件名和行号
ProGuard
-keepattributes SourceFile.LineNumeberTable
- 测试版本包,渠道号区分开
- Java语法相关异常
- NPE: 对参数,返回数据判空,避免过多使用全局变量
- IndexOutOfBoundsExceptions,StringIOBE,ArrayIOBE,String.subString(),ListView操作不当: 数组、集合为空,长度,取值下标不存在
- invoke virtual method on a null object reference: 对象为空,实例化对象回收为空
- ClassCastException: 类型转换函数,转换时返回默认值
- NumberFormatException: 数据类型转换,服务器返回数据类型错误,转换失败返回默认值
- NegativeArraySizeException: 数组大小为负值异常
- ConcurrentModificationException: 遍历集合同时,删除元素,多线程删除同一个集合,使用并发库线程安全集合类
- Comparison method violates its general contract: 比较器使用不当,对自定义比较器进行单元测试,返回-1,1,0
- ArithmeticException: 除数为0,GifView 中movie的duration如果为0,会抛出此异常,设置默认值1
- UnsupportedOperationException,List.remove(),Collection.remove(): Arrays.asList() 返回Arrays$ArrayList 此类没有实现add() remove();
- ClassNotFoundException: Class.forName("com.aaa.bbb"),找不到此类,混淆造成类名改变,类似的有 ClassLoader.findSystemClass("name")或者loadClass()
- NoClassDeFoundError: A,B 两个类位于不同的dex中,如果A类所在dex中被删除,运行时就会抛出此异常,通常由于插件化编程造成的异常,因为要使用DexClassLoader,或者第三方SDK
- 四大组件相关异常
- ActivityNotFoundExc: No Activity found to handle intent原因是url不是以http开头的,或者打开SD卡上HTML页面时,没有为intent指定浏览器
intent=new Intent(Intent.Action_View,Uri.parse("file:https:///sdcard/101.html"))
intent.setClassName("com.android.provider","com.android.browser.BrowserActivity")
调用第三方APK,未安装造成的异常
- RE,Unable to instantiate activity ComponentInfo: 没有注册Activity
- RE,Unable to instantiate receiver: 检查Manifest.xml,混淆所造成额名称错误
- Unable to start receiver : startActivity intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- Failure delivering result ResultInfo:StartActivityForResult() 返回数据异常造成
- Fragment not attached to activity: Fragment 没有attach to activity,调用getResources(),判读isAdded()
- 序列化异常
- Parcelable encountered IOExc writing serializable object(name=xxx)..: JSONObj,JSONAry 不支持序列化,成员变量不支持序列化
- BadParcelableExc ClassNotFoundExc when unmarshalling:未指定ClassLoader
ClassLoader为空时,系统会采取默认的ClassLoader Android ClassLoader: frameworkLoader(加载Android系统内部的类),apk ClassLoader(加载自定义的类,系统的类) APP启动时,默认ClassLoader是apk ClassLoader,系统内存不足应用被回收再次启动时会再次启动,这个默认ClassLoader会变为framework ClassLoader 所以对于我们自己的类会抛出ClassNotFoundExc.
- Parcelable encountered IOExc reading serializable object: ProGuard 对于Class.forName(class) 中的class不起作用,反序列化时找不到类,相应解决办法是-keep class
- Parcelable encountered IOExc writing serializable object: app使用getSerializableExtra()没有做异常判断,反序列化时传入畸形数据,导致本地拒绝服务,传入Integer抛出ClassCastExc,传入自定义可序列化对象 ClassNotFoundExc
- Could not read input channel file descriptors from parcel: Intent 传的数据太大,或者FileDescriptor过多没有关闭,looper太多没有退出
- 列表相关异常
- ListView IllegalStateException: “The content of the adapter has changed but ListView did not receive a notification” Make sure the content of your adapter is not modified from a background thread , but only from the UI thread
Activity.runOnUiThread() 调用Handler,通知主线程修改adapter 确保列表同步更新,调用notifyDataSetChanged()
- 滚动时点击刷新按钮,崩溃. IndexOutOfBoundsExc,throwIndexOutOfBoundsExc,ArrayList.get() at HeaderViewListAdapter.getView()
滚动时获取getCounts()=30,getView()方法中,当数据清空size=1(HeaderViewListAdapter),Invalid index 30. 解决方法是滚动时设置刷新按钮不可点击
- AbsListView 的obtainView 返回空指针:NPE,AbsListView.obtain View at ListView.makeAndAddView
obtainView获取不到view,getView()方法返回null,判断getView返回值为null时,返回convertView,ConvertView不为空
- Adapter数据源变化但是没调用notifyDataSetChanged:在初始化viewpager时,先初始化adapter的数据,再传给viewpager, 如果不这样处理,更新adapter内容后调用notifyDataSetChanged
- 窗体相关异常(dismiss 对话框的时候,activity已经不存在)
- Activity has leaked window that was originally added here: activity finish() 后dialog 还存在,窗口句柄泄露,未能及时销毁,在OnDestroy()中 dismiss dialog
- View not attache to window manager: 在耗时任务开始时,显示一个对话框,当任务完成销毁对话框,如果在此期间, activity被销毁重启,dismiss的时候WindowManager发现Dialog 所属activity不存在,所以会报View not attached to windowManager.
不要在非UI线程使用对话框,activity有相应对话框回调:onCreateDialog(),showDialog(),dismissDialog(),removeDialog() is deprecated,使用DialogFragment代替 Dialog对象在Activity可控范围之内和生命周期内,可以override dismiss(),在dismiss之前,判断activity是否存在 窗体在不恰当的时候获取了焦点:NPE, PopupWindow$PopupViewContainer.dispatchKeyEvent,原因popupWindow在显示之前, 就获取了焦点,导致crash,4.0对这类问题进行规避,2.3兼容,在showAtLocation()后调用setFocusable(true),dismiss后调用setFocusable(false),setFocusable()为了让控件实现监听 Unable to add window-- token null is not for an application: Context 不正确,Dialog.Builder(getApplicationContext()) 是错误的,应该是Activity. Permission denied for this window type:BadTokenExc. 在使用WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 涉及window type 权限,没有设置权限 添加 系统窗口显示在其他应用顶部,屏幕顶部窗体覆盖在window BadTokenExc,is your activity running: activity不存在了,或者在onCreate()中,显示popupWindow,因为需要parent,依附于activity
- Adding window failed: Crash 在Android源码,ViewRoot的setView方法中
- NPE,AlertDialog.resolveDialogTheme: Activity A 调用 Activity B 的dialog.show(),解决方法, 通知对话框工具类,BActivity;TabActivity中切换tab时Crash,设置dialog.show(getParent())为父级Activity
- The specified child already has a parent,you must call removeView() on the child's parent first: imageView 在LinearLayout parent中,onCreate()方法setContentView(imageView)会Crash, 必须先parent.removeView(child),再设置setContentView(child)
- 子线程不能修改UI,操作AlertDialog,Toast:Cant't create handler inside that has not called Looper.prepare(), ViewRootImpl 中checkThread() Crash,解决办法handler,runOnUiThread(),Looper.getMainLooper(),或者是Looper.prepare(),Looper.loop()
@Override
public void requestFitSystemWindows() {
checkThread();
mApplyInsetsRequested = true;
scheduleTraversals();
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
- Resources$NotFoundExc,id错误,找不到正确的res
- StackOverflowError:布局嵌套太多,不要超过5层,移除不必要视图,app退出时,有多个线程未关闭,需要使用System.exit(0),无论是那种情况,都是由于无限递归,JVM中有个栈,预设了一个深度,超出就会Crash
- UnsatisfiedLinkedError: so格式文件没有加载到,检查libs目录文件
CPU指令集,armeabi,armeabi-v7a,mips,x86.armeabi,v7a的so数量不一致,典型会导致此Crash
- InflateExc
FileNotFoundExc:Activity销毁,涉及的资源没有被回收,产生内存泄漏,找不到这个资源 InflateExc,缺少构造器,super(context,attributeSet) InflateExc style和android:textStyle 的区别
- TransactionTooLargeExc,Binder最大限制为1M,大于1M就会Crash,不要将大量数据传入Binder,比如说图片
- 系统碎片化相关Crash
- NoSuchMethodError,SDK版本不一致造成,deprecated方法,Android Lint 检查,版本判断
- RemoteViews,使用在Appwidget ,Notification,当绑定Notification时,Bitmap为null,String为""或null,版本为4.0时Crash,在4.1以上并不会导致程序崩溃
- PointIndex out of range: IllegalArgumentExc,由于Android系统原因导致的,简单有效办法是在绘图时捕获这个异常, 重写View的OnInterceptTouchEvent,OnTouchEvent,增加try...catch,如果是ViewPager,OnInterceptTouchEvent返回false,导致ViewPager翻页出现bug
- SecurityExc
Intent 图片数据太大 动态加载其他APK,通过ContextHolder注册BroadcastReceiver,把APK重新部署即可 NoPermission to modify thread,ROM禁止这些权限,判断权限,PackageManager.checkPermission(...PackageManager.PERMISSION_GRANTED)
- View的getDrawingCache()返回null,NPE,当图片太大,超出Cache大小Crash
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"buildDrawingCache/SW Layer for " + getClass().getSimpleName());
}
try {
buildDrawingCacheImpl(autoScale);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
/**
* private, internal implementation of buildDrawingCache, used to enable tracing
*/
private void buildDrawingCacheImpl(boolean autoScale) {
mCachingFailed = false;
int width = mRight - mLeft;
int height = mBottom - mTop;
final AttachInfo attachInfo = mAttachInfo;
final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
if (autoScale && scalingRequired) {
width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
}
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
final long drawingCacheSize =
ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
>>> if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
if (width > 0 && height > 0) {
Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+ " too large to fit into a software layer (or drawing cache), needs "
+ projectedBitmapSize + " bytes, only "
+ drawingCacheSize + " available");
}
destroyDrawingCache();
mCachingFailed = true;
return;
}
……
}
- DeadObjectException - The object you are calling has died, because its hosting process no longer exists.
- Android 2.1不支持SSL,判断版本不支持操作
- Android 2.2不支持xlargeScreen,添加2.3(API 9)
- ViewFlipper,Receiver not registered: 横竖屏切换造成,因为OnDetachedFromWindow()在onAttachedToWindow()之前别调用所致, 重写ViewFlipper的onDetachedFromWindow()
protected void onDetachedFromWindow(){
try{
super.OnDetachedFromWindow()
}catch{
stopFlipping();
}
}
- ActivityNotFoundExc,WirelessSettings,4.0以上把打开网络设置方式舍弃了
if(Builder.Version.SDK_INT>13){
startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS));
}else{
startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS));
}
- PackageManage has died,try...catch
- SpannableString 富文本,IOBE,setSpan,某些Android系统对于getSelectionStart()会返回-1,这样就会Crash,判断是否为-1
- Can not perform this action after onSaveInstance(), commit 方法在Activity 的OnSaveInstanceState()之后会出错,Activity保存完状态后再添加Fragment 会出错,commitAllowingStateLoss()
- Service intent must be explicit,显示启动service,指定Component或者package
- SQLite相关异常
- NoTransaction is active,逐条循环插入大量数据时,会Crash,一条语句是一个事务,采用批量插入语法,一次性插入数据库 db.setTransaction() 在这个方法执行前,所有的execSql()都不会更新到数据库,等这个方法执行完后会一次性把所有的执行完
- 忘记关闭Cursor,内存泄漏,手动关闭Cursor.close();
- 数据库被锁定,不同线程创建多个连接会Crash,解决办法是把数据库做成单列,对于多进程APP,需要ContentProvider
- 试图打开已关闭对象,不同线程同事操作数据库,当前聊天室一直打开数据库,在退出时再close()
- 文件加密了,或无数据库,SQLiteDatabaseCorruptExc,file is encrypted or is not a database:注意DB文件版本, 统一成一个版本,APP安装在SD卡上,多次插拔就会导致文件破损
- WebView缓存导致的崩溃,多线程操作增删数据Crash, SQLiteDiskIOExc,WebViewDataBase,使用了webview缓存技术
Webview两种缓存:网页数据缓存,存储打开过的页面及资源,Html5缓存,aapcache url 保存在webviewCache文件夹下,对于webview.db ,webviewCache.db 自动生成一个android_metadata 表, 只要创建SQLite数据库中的表,就会自动创建这个表,只有一个local字段,存放的是en-US ,zh-CN
- android_metadata表不存在,no such table
SQLiteDatabase db=SQLiteDatabase.openDatabase(PATH,null,SQLiteDatabase.OPEN_READONLY);
改为
SQLiteDatabase db=SQLiteDatabase.openDatabase(PATH,null,SQLiteDatabase.NO_LOCALIZED_COLLATORS);
- 其他异常
- OOM ,android:largeHeap="true",官方说减少内存使用, 使用回收和复用的方法
- TimeOutExc,GC回收时抛出异常,重写finalize,不要有超时操作
- JSONExc,使用optString(),optJsonArray(),new JSONArray(jsonString) jsonString为空时crash
- 第三方SDK crash
- views has same id
- LayoutInflater.from().inflate()使用不当导致的崩溃,应该在当前使用的子类中实例化
- ViewGroup ,parameter must be a descent of this view,保证当前view获取焦点
/**
* Helper method that offsets a rect either from parent to descendant or
* descendant to parent.
*/
void offsetRectBetweenParentAndChild(View descendant, Rect rect,
boolean offsetFromChildToParent, boolean clipToBounds) {
// now that we are up to this view, need to offset one more time
// to get into our coordinate space
if (theParent == this) {
if (offsetFromChildToParent) {
rect.offset(descendant.mLeft - descendant.mScrollX,
descendant.mTop - descendant.mScrollY);
} else {
rect.offset(descendant.mScrollX - descendant.mLeft,
descendant.mScrollY - descendant.mTop);
}
} else {
throw new IllegalArgumentException("parameter must be a descendant of this view");
}
}
- Monkey点击过快导致的崩溃,点击事件加延迟函数
public boolean isWindowLocked(){
long current=SystemClock.elapseRealTime();
if(current-mLastOnClickTime>500){
mLastOnClickTime=curretn;
return false;
}
return true;
}
- IllegalArgumentsExc:bitmap size exceeds 32bits,图片缩放很多倍,内存溢出Crash,多发生全屏显示一张图片,try..catch
//srcW ,srcH 缩放前
//targetW,targetH 缩放后
public void scale(){
float scaleW=targetW/srcW;
float scaleH=targetH/srcH;
Matrix matrix=new Matrix();
matrix.postScale(scaleW,scaleH);
}
/**
* Postconcats the matrix with the specified scale.
* M' = S(sx, sy) * M
*/
public boolean postScale(float sx, float sy) {
native_postScale(native_instance, sx, sy);
return true;
}
- 没有加载到图片,或者缓存数据被清空,提前调用了获取图片宽高的方法,图片宽高为0,try-catch
- View xxx has already been added to the window manager ,不能重复添加组件
try-catch WindowManager.removeView try-catch WindowManager.addView