Android开发个人笔记

这个笔记主要记录了开发中遇到的问题和解决方案,还有一些源码技巧。

• Android通过Intent发送电子邮件含附件

Intent emailSelectorIntent = new Intent(Intent.ACTION_SENDTO);
emailSelectorIntent.setData(Uri.parse("mailto:"));

final Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"address@mail.com"});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
emailIntent.putExtra(Intent.EXTRA_TEXT, "Text");
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
emailIntent.setSelector(emailSelectorIntent);

if (myFile.exists()) {
    Uri attachment = null;
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            attachment = FileProvider.getUriForFile(context, "my_fileprovider", myFile);
        } else {
            attachment = Uri.fromFile(myFile);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    emailIntent.putExtra(Intent.EXTRA_STREAM, attachment);
}

if (emailIntent.resolveActivity(context.getPackageManager()) != null) {
    startActivity(emailIntent);
}

• Android分享视频到其他平台

public static boolean checkAppInstalled(Context context, String packageName) {
     if (TextUtils.isEmpty(packageName)) {
         return false;
     }

     PackageInfo packageInfo = null;
     try {
         packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
     } catch (PackageManager.NameNotFoundException e) {
         e.printStackTrace();
     }

     return packageInfo != null;
 }

 public static Intent createShareIntent(Context context, String filePath, String packageName, String title) {
     String type = "video/*";
     Intent intent = new Intent(Intent.ACTION_SEND);
     intent.setType(type);
     if (!TextUtils.isEmpty(filePath)) {
         File media = new File(filePath);
         Uri uri = null;
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             try {
                 uri = FileProvider.getUriForFile(context, "provider_name", media);
             } catch (Exception e) {
                 e.printStackTrace();
             }
         } else {
             uri = Uri.fromFile(media);
         }
         intent.putExtra(Intent.EXTRA_STREAM, uri);
         intent.putExtra(Intent.EXTRA_SUBJECT, "#APP_NAME");  //设置此项可以在分享平台看到标签
     }

        if (!TextUtils.isEmpty(packageName)) {
            intent.setPackage(packageName);
        }

        return Intent.createChooser(intent, title);
    }

• ImageView方法setImageUri导致OOM

获取到本地图片的Uri以后直接通过setImageUri导致了OutOfMemoryError

分析: 使用setImageUri是直接对uri对应的图片进行加载的,如果图片过大,就会造成OOM

解决: 使用Glide加载,或者对图片进行压缩处理后再设置

• 一个HashCode的生成方法

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;
    }
}

• App更新时出现解析程序包时出现问题

下载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();
}

• Activity.onActivityResult()提前执行执行

使用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去掉。

• Android安装应用界面点击打开带来的问题

应用安装完成后,在系统的安装界面有“完成”和“打开”两个按钮。当用户点击“打开”按钮进入应用后,若此时再按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;
}

• Context使用场景

为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Context的都能替换为ApplicationContext

ApplicationActivityServiceContentProviderBroadcastReceiver
Show Dialog
Start Activity
Layout Inflation
Start Service
Bind Service
Send Broadcast
Regist BroadcastReceiver
Load Resource Value

• Java四种引用类型对比表

对象引用:强引用 > 软引用 > 弱引用 > 虚引用

引用类型回收时机用途生存时间
强引用从来不会对象的一般状态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卡插拔的情况下进行全盘扫描,不是实时的而且代价比较大,所以单个文件的刷新很有必要。

• ListView的局部刷新

直接调用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);
    }
}

注意:更新视图以后最后那个列表数据也必须更新,否则由于数据源不变,滚动后又会还原。

• singleInstance无法传递值

在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启动,因此就可以获取传递过来的数据进行相关处理。

• 捕获Home键

重写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>

如果两者都要实现的话,就必须配两个过滤器。

• 通过View获取Activity

继承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清除历史记录

直接调用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一个生命周期的思考

思考:启动一个Activity(A),并在其onCreate/onStart方法中启动另一个Activity(B),生命周期表现如下:

  • 启动 A.onCreate->A.onStart->A.onResume->A.onPause->B.onCreate->B.onStart->B.onResume->A.onSaveInstanceState->A.onStop
  • 退出 B.onPause->A.onRestart->A.onStart->A.onResume->B.onStop->B.onDestroy->A.onPause->A.onStop->A.onDestroy

• 一个比较好的ViewHolder

    public static class ViewHolder {
        @SuppressWarnings("unchecked")
        public static <T extends View> T get(View view, int id) {
            SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
            if (viewHolder == null) {
                viewHolder = new SparseArray<>();
                view.setTag(viewHolder);
            }
            View childView = viewHolder.get(id);
            if (childView == null) {
                childView = view.findViewById(id);
                viewHolder.put(id, childView);
            }
            return (T) childView;
        }
    }

• 代码设置selectableItemBackground

通常会在XML中设置?selectableItemBackground来实现点击波纹效果:

<View
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="?selectableItemBackground" />

那如何通过代码设置呢?

TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
view.setBackgroundResource(outValue.resourceId);

• 线性进度条样式

 <ProgressBar
            android:id="@+id/progress_bar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginStart="30dp"
            android:layout_marginEnd="30dp"
            android:indeterminate="false"
            android:maxHeight="4dp"
            android:minHeight="4dp"
            android:progressDrawable="@drawable/progress_bar" />
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="4dp" />
            <solid android:color="@color/colorAccent" />
        </shape>
    </item>

    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="4dp" />
                <solid android:color="@color/colorPrimary" />
            </shape>
        </clip>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="4dp" />
                <solid android:color="@color/colorPrimary" />
            </shape>
        </clip>
    </item>
</layer-list>

以上为个人在项目过程中遇到的问题和解决方法记录,如有错误请指正,如需转载请标明原文出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值