这个笔记主要记录了开发中遇到的问题和解决方案,还有一些源码技巧。
获取到本地图片的Uri以后直接通过setImageUri导致了OutOfMemoryError
分析: 使用setImageUri是直接对uri对应的图片进行加载的,如果图片过大,就会造成OOM
解决: 使用Glide加载,或者对图片进行压缩处理后再设置
public class Actor {
private final int id;
private final String name;
private final int rating;
private final int yearOfBirth;
public Actor(int id, String name, int rating, int yearOfBirth) {
this.id = id;
this.name = name;
this.rating = rating;
this.yearOfBirth = yearOfBirth;
}
// getter
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Actor actor = (Actor) o;
if (id != actor.id) return false;
if (rating != actor.rating) return false;
if (yearOfBirth != actor.yearOfBirth) return false;
return name != null ? name.equals(actor.name) : actor.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + rating;
result = 31 * result + yearOfBirth;
return result;
}
}
下载apk存放到Context.getFilesDir()也就是data/data/packageName/files这个路径下然后调用系统安装程序进行安装时出现解析程序包时出现错误。
分析:Context.getFilesDir()目录下apk的权限位(-rw-------)只有当前应用可读可写,但跳转到安装界面是属于别的应用程序,没有权限执行解析操作,于是就会出现解析程序包时出现问题。
解决:通过在代码中写入Linux指令修改此apk文件的权限,改为全局可读可写可执行:
final String[] command = {"chmod", "777", file.getPath()};
ProcessBuilder builder = new ProcessBuilder(command);
try {
builder.start();
} catch (IOException e) {
e.printStackTrace();
}
使用startActivityForResult启动Activity还没等到被调用的Activity返回,**onActivityResult()**就被执行了。
分析:与Activity的启动模式(launchMode)有关,该属性可以在AndroidManifest.xml中设置,也可以通过Intent.addFlags(int flags)设置。
所有需要传递或接收的Activity的启动模式不允许设置为singleInstance,或只能设为标准模式,一个singleInstance模式的Activity将会是它所在的任务中唯一的Activity。如果它启动了别的Activity,那个Activity将会依据它自己的加载模式加载到其它的任务中去──如同在Intent中设置了FLAG_ACTIVITY_NEW_TASK标记一样的效果。也就是说这两种情况下,新开启的Activity一定在新的任务(进程)中,和原来的Activity不在同一进程中,这就是系统在**startActivityForResult()后直接调用onActivityResult()**方法的原因。
解决:将目标Activity的启动模式singleInstance/singleTask去掉。
应用安装完成后,在系统的安装界面有“完成”和“打开”两个按钮。当用户点击“打开”按钮进入应用后,若此时再按Home键切换到桌面,再从桌面点击应用程序图标试图切回应用时,应用程序重新启动了,此时之前启动的应用其实还在后台运行。然而当用户“完全退出”应用,或者在安装完成界面直接点击“完成”按钮再从桌面启动,或者此应用之前是存在的“覆盖安装”后点击“打开”按钮都是不会导致应用程序“多次启动”的。
分析:开启应用的类型不同导致,ADB方式安装的Intent没有Action和Category,从Launcher启动时Intent的Action为android.intent.action.MAIN并且Category为android.intent.category.LAUNCHER,系统程序安装器中启动时Intent的Action为android.intent.action.MAIN,但是没有Category。因此只要开启过应用通过标记判断如果Task中已经打开过(应用程序栈已经有此Activity并且是根路径的Activity)则跳过不打开。
解决:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
// Activity was brought to front and not created,
// Thus finishing this will get us to the last viewed activity
finish();
return;
}
// Regular activity creation code...
}
另外一种解决:
if (!isTaskRoot()) {
finish();
return;
}
为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Context的都能替换为ApplicationContext
Application | Activity | Service | ContentProvider | BroadcastReceiver | |
---|---|---|---|---|---|
Show Dialog | 否 | 是 | 否 | 否 | 否 |
Start Activity | 否 | 是 | 否 | 否 | 否 |
Layout Inflation | 否 | 是 | 否 | 否 | 否 |
Start Service | 是 | 是 | 是 | 是 | 是 |
Bind Service | 是 | 是 | 是 | 是 | 否 |
Send Broadcast | 是 | 是 | 是 | 是 | 是 |
Regist BroadcastReceiver | 是 | 是 | 是 | 是 | 否 |
Load Resource Value | 是 | 是 | 是 | 是 | 是 |
对象引用:强引用 > 软引用 > 弱引用 > 虚引用
引用类型 | 回收时机 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 在垃圾回收时 | 对象缓存 | GC运行后终止 |
虚引用 | 在垃圾回收时 | 对象跟踪 | GC运行后终止 |
现在很多图片库需要给图片设置一个最大缓存,但是这个值设置多少合适呢?
高端机和低端机的配置显然应该不同,可以考虑设置一个动态值。
建议设置为应用可用内存的1/8:
int memoryCache = (int) (Runtime.getRuntime().maxMemory() / 8);
第三方软件(如音乐APP)下载文件或删除文件之后,需要第三方软件主动触发媒体库进行更新。
// 通知媒体库更新单个文件状态
Uri fileUri = Uri.fromFile(file);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, fileUri));
媒体库会在手机启动,SD卡插拔的情况下进行全盘扫描,不是实时的而且代价比较大,所以单个文件的刷新很有必要。
直接调用notifyDataSetChanged()刷新列表代价有点高,最好能局部刷新。
局部刷新的重点是,找到要更新的那项的View,然后再根据业务逻辑更新数据即可。
private void updateItem(int index) {
int visiblePosition = listView.getFirstVisiblePosition();
if (index - visiblePosition >= 0) {
// 得到要更新的item的view
View view = listView.getChildAt(index - visiblePosition);
// 更新界面(示例参考)
// TextView nameView = ViewLess.$(view, R.id.name);
// nameView.setText("update " + index);
// 更新列表数据(示例参考)
// list.get(index).setName("Update " + index);
}
}
注意:更新视图以后最后那个列表数据也必须更新,否则由于数据源不变,滚动后又会还原。
在AndroidManifest.xml文件中设置android:launchMode=“singleInstance”,可以保证栈中每个Activity只有一个实例,防止重复界面的不断加载。单纯的跳转页面时是可以处理的,但是跳转界面需要传值时就会出问题,这样处理只会将后台的Activity启动,传递的值是无法获取并重新加载的,如:ActivityA ——> ActivityB ——(搜索关键字)——> ActivityA(当我从ActivityB传递关键字到ActivityA时,只是将栈底的ActivityA放在了栈顶,并不会做其他操作)如果既要保证每个Activity只有一个实例,又可以传递数据,可以在跳转界面的代码处加上下面一句话:intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);这样处理,就是在跳转的时候将堆栈中该Activity前面的所有Activity都清除,并重新发intent将此Activity启动,因此就可以获取传递过来的数据进行相关处理。
重写onAttachedToWindow()方法,在方法里面setType 即可,去掉之后就无法捕获Home键:
@Override
public void onAttachedToWindow() {
// TODO Auto-generated method stub
this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
super.onAttachedToWindow();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_HOME) {
//不做任何操作
}
return false;
}
如果在文件夹下选择视频文件时,想调用自己的播放器,需要在Manifest.xml中设置过滤器,设置如下:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
intent-filter>
如果想在浏览器中调用自己的播放器,设置如下:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:mimeType="video/*"
android:scheme="http" />
intent-filter>
如果两者都要实现的话,就必须配两个过滤器。
继承AppCompatActivity以后,Android会将应用控件转成v7包中对应的控件,Context也会替换成TintContextWrapper,所以View#getContext()方法获取的可能不是Activity而是TintContextWrapper。
public static Activity getActivity(View view) {
if (null == view) return null;
Context context = view.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
直接调用WebView.clearHistory无效,必须在doUpdateVisitedHistory中清除。
webView.setWebViewClient(new WebViewClient() {
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
super.doUpdateVisitedHistory(view, url, isReload);
if (needClearHistory) {
needClearHistory = false;
view.clearHistory();//清除历史记录
}
}
});
思考:启动一个Activity(A),并在其onCreate/onStart方法中启动另一个Activity(B),生命周期表现如下:
以上为个人在项目过程中遇到的问题和解决方法记录,如有错误请指正,如需转载请标明原文出处!