1. 打开菜单File->Settings
2. 打开左侧导航列表Tools->External Tools
3. 点击增加外部工具按钮,在弹出的按钮输入下图中的内容:
4. 补充:去掉Open console前面的对勾
5. 点击ok完成后,我们在文件列表或者代码编辑器上方的文件名上右键,在菜单中选择”External Tools”,然后选择新建的工具就可以方便的在文件管理器中查看文件了
博文背景:博主要实现app的左侧导航按钮功能,因为要实现某个导航按钮被选中的效果,所以没有使用ListView,而是用的RadioGoup来实现.好,RadioGroup嵌套多个RadioButton很简单的实现了导航列表的功能.另外一个需求是给导航按钮们增加分隔线,博主首先使用RadioButton的drawableTop,分隔线不在最顶部,而且水平不居中,这个属性肯定是不行。然后比较笨的方法就是插入多个ImageView,设置图片为分隔线,但是……导航按钮多的话,这样做真的很麻烦。
于是博主开始研究RadioGroup的属性,发现有一个divider属性,这在ListView里是分隔线的意思,于是给RadioGroup设置这个属性,运行程序,分隔线不显示,心想RadioGroup应该不支持这个属性,毕竟通常情况下divider是对List形式组件才有效的。再想想要加n个ImageView实在不甘心,度娘了一下,原来要再加个showDivider属性。
showDivider有三个可选项,beginning、middle、end,分别对应最开始的分隔线,各RadioButton中间的分隔线,最结尾的分隔线。我们可以如下设置:
1 |
android:showDividers="beginning|middle|end" |
不是true或者false哦 ;-)
下面是一个完整的Activity部局的xml代码,在你的res/drawable中放一个叫timg.png的图片,然后把下面代码粘贴到你的Activity的xml文件中,运行一下就可以看到效果了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:divider="@mipmap/timg" android:showDividers="beginning|middle|end" tools:context="com.chinatsp.dividertestapp.MainActivity"> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:visibility="gone" android:text="CheckBox"/> <RadioButton android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:visibility="visible" android:text="RadioButton"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Button"/> </LinearLayout> |
注意事项:
如下图,第二行的ImageView和TextView的layout_width属性都为wrap_content,layout_weight都为1,所以两者显示的权重并不是1:1
第四行ImageView和TextView的layout_width属性都为0dp,layout_weight都为1,所以两者显示的权重是1:1
笔者猜测应该是layout_content的值被优先计算,然后再计算layout_weight的值,所以造成第二行的问题
xml部局如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp" tools:context=".activities.LayoutWeightTestActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:orientation="horizontal"> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/aquamarine" android:text="@string/str_helloworld" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/burlywood" android:text="@string/str_helloworld" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:orientation="horizontal"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:adjustViewBounds="false" app:srcCompat="@drawable/ic_launcher_background" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/coral" android:text="@string/str_helloworld" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:orientation="horizontal"> <ImageView android:id="@+id/imageView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/luantailang" /> <ImageView android:id="@+id/imageView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/luantailang" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:orientation="horizontal"> <ImageView android:id="@+id/imageView4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" app:srcCompat="@drawable/ic_launcher_background" /> <TextView android:id="@+id/textView4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@color/deepskyblue" android:text="TextView" /> </LinearLayout> </LinearLayout> |
部局的xml如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:baselineAligned="false"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:orientation="vertical" android:padding="10dp"> <TextView android:id="@+id/tvSongName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:ellipsize="marquee" android:focusable="auto" android:focusableInTouchMode="true" android:text="光辉岁月" android:textSize="18sp" android:textStyle="bold" /> <TextView android:id="@+id/tvSinger" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_weight="1" android:ellipsize="end" android:text="演唱:Beyond" /> <TextView android:id="@+id/tvAlbum" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_weight="1" android:ellipsize="end" android:text="专辑:光辉岁月" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:id="@+id/tvPlayPosition" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="5dp" android:text="00:00" /> <ProgressBar android:id="@+id/progressBar2" style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" /> <TextView android:id="@+id/tvDuration" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:text="00:00" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" android:orientation="horizontal"> <ImageView android:id="@+id/imgCover" android:layout_width="match_parent" android:layout_height="match_parent" android:adjustViewBounds="true" android:minWidth="150dp" android:minHeight="150dp" android:scaleType="centerInside" android:src="@drawable/default_cover" /> </LinearLayout> </LinearLayout> |
部局的预览图如下
音乐标题(光辉岁月)的ellipsize属性为marquee,当设置专辑下面的当前播放进度的文本时音乐标题的marquee总是重新播放,应该是进度TextView的文本改变影响了父级的LinearLayout(按道理来说不应该影响的)。
解决办法:为父级的LinearLayout增加android:baselineAligned=”false”属性
一、下载Android6.0源码
大家可以从清华的镜像站下载源码库,具体参数https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/和网络上的教程
二、安装编译时需要的软件
直接连接Ubuntu速度比较慢,而且Ubuntu官方没有openjdk7的安装包了,所以更换更新源到国内的源,我把阿里和163的源都放进去了
1.先备份更新源
1 |
cp /etc/apt/sources.list /etc/apt/sources.list.20170405 |
2. 增加阿里的源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties deb http://mirrors.aliyun.com/ubuntu/ xenial universe deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties deb http://archive.canonical.com/ubuntu xenial partner deb-src http://archive.canonical.com/ubuntu xenial partner deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse |
3. 增加163的更新源
1 2 3 4 5 6 7 8 9 10 |
deb http://mirrors.163.com/ubuntu/ vivid main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ vivid-security main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ vivid-updates main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ vivid-proposed main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ vivid-backports main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ vivid main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ vivid-security main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ vivid-updates main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ vivid-proposed main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ vivid-backports main restricted universe multiverse |
4. 执行一行apt-get update
1 |
sudo apt-get update |
5. 安装编译时需要的软件
1 2 |
sudo apt-get install openjdk-7-jdk sudo apt-get install curl g++-multilib gcc-multilib lib32ncurses5-dev lib32readline-gplv2-dev lib32z1-dev libxml2-utils |
三、开始编译
四、常见错误及解决办法
1.编译时出现unsupported reloc 43,解决方法如下:
It works to me:
in file /art/build/Android.common_build.mk, find out:
1 2 3 4 5 6 7 |
# Host. ART_HOST_CLANG := false ifneq ($(WITHOUT_HOST_CLANG),true) # By default, host builds use clang for better warnings. ART_HOST_CLANG := true endif |
change to :
1 2 3 4 5 6 7 |
# Host. ART_HOST_CLANG := false ifeq ($(WITHOUT_HOST_CLANG),false) # By default, host builds use clang for better warnings. ART_HOST_CLANG := true endif |
If it still not works,try this in your android root path: cp /usr/bin/ld.gold prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6/x86_64-linux/bin/ld
jni是Java Native Interface的缩写,jni并不是Android开发的特性,而是在java的早期版本中就已经支持了,用于java层和Native层进行通讯的中间桥梁,大部分情况下使用c和c++进行编写,也可以使用其他的语言编写,只要满足约定的接口就可以。
Jni有以下副作用:
1、不再具有跨平台可移植性,如果移植到其他平台,需要开发Native层的相关代码
2、Native层的程序如果有问题将会导致java层的程序崩溃
1、在java代码中定义Native函数,比如java文件名为JniTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class JniTest{ static { System.loadLibrary("jnitest"); } native int calc(int i1, int i2); public static void main(String args[]){ JniTest jnitest = new JniTest(); int sum = jnitest.calc(322, 21); System.out.print(sum); } } |
2、使用javac编译java文件,javac JniTest.java
3、使用javah工具传入java类名,生成native层的头文件,javah JniTest,此时目录下会有一个JniTest.h
4、新建一个JniTest.cc的文件,并添加如下内容:
1 2 3 4 5 6 |
#include "JniTest.h" JNIEXPORT jint JNICALL Java_JniTest_calc (JNIEnv *env, jobject obj, jint i1, jint i2){ return i1 + i2; }; |
5、将navtive层的代码编译成so库,命令如下:
1 2 |
gcc -I/usr/lib/jvm/java-8-openjdk-amd64/include/ -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux -Wall -g -fPIC -c JniTest.cc -o jnitest.o gcc -shared jnitest.o -o libjnitest.so |
6、在java层使用System.loadLibrary(“jnitest”);加载动态库,并调用函数,见第1中的java代码
参考链接:
https://baike.baidu.com/item/JNI/9412164?fr=aladdin
本文总结了几种常用的补间动画效果,具体的使用方法请参考网络上的其他文章:
先总结几点注意事项:
1.放大效果(附加淡入效果)
在res/anim目录下新建zoomin.xml,并粘贴下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator" android:zAdjustment="top"> <scale android:fromXScale="0.3" android:toXScale="1.0" android:fromYScale="0.3" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" android:duration="300" /> <alpha android:fromAlpha="0.1" android:toAlpha="1.0" android:duration="300"/> </set> |
2.缩小效果(附加淡出效果)
在res/anim目录新建zoomout.xml,并粘贴下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator" android:zAdjustment="top"> <scale android:fromXScale="1.0" android:toXScale=".5" android:fromYScale="1.0" android:toYScale=".5" android:pivotX="50%p" android:pivotY="50%p" android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="300"/> </set> |
3. 淡入效果
在res/anim目录新建fadein.xml,并粘贴下面的代码
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha android:fromAlpha="0.1" android:toAlpha="1.0" android:duration="300" /> </set> |
4. 淡出效果
在res/anim目录下新建fadeout.xml,并粘贴下面的代码
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha android:fromAlpha="1.0" android:toAlpha="0.1" android:duration="300" /> </set> |
5. 纵向布幕拉开效果(自中间往上下拉开)
在res/anim目录下新建open_verticaly_from_middle.xml,并粘贴下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator"> <scale android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="0.1" android:toYScale="1.0" android:pivotX="50%p" android:pivotY="50%p" android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="300" /> </set> |
6.纵向布幕关闭效果(自上下往中间关闭)
在res/anim目录下新建close_verticaly_to_middle.xml,并粘贴下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator"> <scale android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="1.0" android:toYScale="0.1" android:pivotX="50%p" android:pivotY="50%p" android:duration="300" /> <alpha android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="300" /> </set> |
7. 从顶部进入
在res/anim中建立文件slide_in_from_top.xml, 并粘贴下面的代码
1 2 3 4 5 6 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="-100%" android:toYDelta="0" android:duration="300"/> </set> |
8. 从底部进入
在res/anim中新建文件slide_in_from_bottom.xml, 并粘贴下面的代码
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:fromYDelta="200%" android:toYDelta="0" android:duration="300"/> </set> |
9. 从顶部划出
在res/anim中新建文件slide_out_to_top.xml, 并粘贴下面的代码
1 2 3 4 5 6 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="0" android:toYDelta="-100%" android:duration="300"/> </set> |
10. 从底部划出
在res/anim中新建文件slide_out_to_bottom.xml, 并粘贴下面的代码
1 2 3 4 5 6 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromYDelta="0" android:toYDelta="200%" android:duration="300"/> </set> |
首先调用Activity.java中的startActivity
1 2 3 |
public void startActivity(Intent intent) { this.startActivity(intent, null); } |
调用了另外一startActivity函数,增加了options参数,为避免过于复杂,我们使用options为null
1 2 3 4 5 6 7 8 9 |
public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } |
实际上又调用到了startActivityForResult函数
1 2 3 |
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } |
同样,带options参数的startActivityForResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { options = transferSpringboardActivityOptions(options); Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { // If this start is requesting a result, we can avoid making // the activity visible until the result is received. Setting // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the // activity hidden during this time, to avoid flickering. // This can only be done when a result is requested because // that guarantees we will get information back when the // activity is finished, no matter what happens to it. mStartedActivity = true; } cancelInputsAndStartExitTransition(options); // TODO Consider clearing/flushing other event sources and events for child windows. } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); } } } |
我们这里假设mParent为null
然后调用Instrumentation的executeStartActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; } |
然后调用ActivityManagerService里的startActivity和startActivityAsUser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) { enforceNotIsolatedCaller("startActivity"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivity", null); // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, null, null, options, userId); } |
mStackSupervisor.startActivityMayWait函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, Bundle options, int userId) { Slog.i(TAG, "startActivityMayWait, liuderu"); // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } boolean componentSpecified = intent.getComponent() != null; // Don't modify the client's object! intent = new Intent(intent); // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profileFile, profileFd, userId); synchronized (mService) { int callingPid; if (callingUid >= 0) { callingPid = -1; } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { callingPid = callingUid = -1; } final ActivityStack stack = getFocusedStack(); stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; if (DEBUG_CONFIGURATION) Slog.v(TAG, "Starting activity when config will change = " + stack.mConfigWillChange); final long origId = Binder.clearCallingIdentity(); if (aInfo != null && (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Check to see if we already // have another, different heavy-weight process running. if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { if (mService.mHeavyWeightProcess != null && (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { int realCallingUid = callingUid; if (caller != null) { ProcessRecord callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { realCallingUid = callerApp.info.uid; } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); ActivityOptions.abort(options); return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, "android", realCallingUid, userId, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); Intent newIntent = new Intent(); if (requestCode >= 0) { // Caller is requesting a result. newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target)); if (mService.mHeavyWeightProcess.activities.size() > 0) { ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, hist.packageName); newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, hist.task.taskId); } newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, aInfo.packageName); newIntent.setFlags(intent.getFlags()); newIntent.setClassName("android", HeavyWeightSwitcherActivity.class.getName()); intent = newIntent; resolvedType = null; caller = null; callingUid = Binder.getCallingUid(); callingPid = Binder.getCallingPid(); componentSpecified = true; try { ResolveInfo rInfo = AppGlobals.getPackageManager().resolveIntent( intent, null, PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } } } } int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, startFlags, options, componentSpecified, null); if (stack.mConfigWillChange) { // If the caller also wants to switch to a new configuration, // do so now. This allows a clean switch, as we are waiting // for the current activity to pause (so we will not destroy // it), and have not yet started the next activity. mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); stack.mConfigWillChange = false; if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating to new configuration after starting activity."); mService.updateConfigurationLocked(config, null, false, false); } Binder.restoreCallingIdentity(origId); if (outResult != null) { outResult.result = res; if (res == ActivityManager.START_SUCCESS) { mWaitingActivityLaunched.add(outResult); do { try { mService.wait(); } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); } else if (res == ActivityManager.START_TASK_TO_FRONT) { ActivityRecord r = stack.topRunningActivityLocked(null); if (r.nowVisible) { outResult.timeout = false; outResult.who = new ComponentName(r.info.packageName, r.info.name); outResult.totalTime = 0; outResult.thisTime = 0; } else { outResult.thisTime = SystemClock.uptimeMillis(); mWaitingActivityVisible.add(outResult); do { try { mService.wait(); } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); } } } return res; } } |
mStackSupervisor.startActivityLocked函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
final int startActivityLocked(IApplicationThread caller, Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity) { int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; if (caller != null) { callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { callingPid = callerApp.pid; callingUid = callerApp.info.uid; } else { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); err = ActivityManager.START_PERMISSION_DENIED; } } if (err == ActivityManager.START_SUCCESS) { final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); } ActivityRecord sourceRecord = null; ActivityRecord resultRecord = null; if (resultTo != null) { sourceRecord = isInAnyStackLocked(resultTo); if (DEBUG_RESULTS) Slog.v( TAG, "Will send result to " + resultTo + " " + sourceRecord); if (sourceRecord != null) { if (requestCode >= 0 && !sourceRecord.finishing) { resultRecord = sourceRecord; } } } ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; int launchFlags = intent.getFlags(); if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { ActivityOptions.abort(options); return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; resultWho = sourceRecord.resultWho; requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; if (resultRecord != null) { resultRecord.removeResultsLocked( sourceRecord, resultWho, requestCode); } if (sourceRecord.launchedFromUid == callingUid) { // The new activity is being launched from the same uid as the previous // activity in the flow, and asking to forward its result back to the // previous. In this case the activity is serving as a trampoline between // the two, so we also want to update its launchedFromPackage to be the // same as the previous activity. Note that this is safe, since we know // these two packages come from the same uid; the caller could just as // well have supplied that same package name itself. This specifially // deals with the case of an intent picker/chooser being launched in the app // flow to redirect to an activity picked by the user, where we want the final // activity to consider it to have been launched by the previous app activity. callingPackage = sourceRecord.launchedFromPackage; } } if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { // We couldn't find a class that can handle the given Intent. // That's the end of that! err = ActivityManager.START_INTENT_NOT_RESOLVED; } if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. err = ActivityManager.START_CLASS_NOT_FOUND; } if (err != ActivityManager.START_SUCCESS) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } setDismissKeyguard(false); ActivityOptions.abort(options); return err; } final int startAnyPerm = mService.checkPermission( START_ANY_ACTIVITY, callingPid, callingUid); final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid, callingUid, aInfo.applicationInfo.uid, aInfo.exported); if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } setDismissKeyguard(false); String msg; if (!aInfo.exported) { msg = "Permission Denial: starting " + intent.toString() + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ")" + " not exported from uid " + aInfo.applicationInfo.uid; } else { msg = "Permission Denial: starting " + intent.toString() + " from " + callerApp + " (pid=" + callingPid + ", uid=" + callingUid + ")" + " requires " + aInfo.permission; } Slog.w(TAG, msg); throw new SecurityException(msg); } boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); if (mService.mController != null) { try { // The Intent we give to the watcher has the extra data // stripped off, since it can contain private information. Intent watchIntent = intent.cloneFilter(); abort |= !mService.mController.activityStarting(watchIntent, aInfo.applicationInfo.packageName); } catch (RemoteException e) { mService.mController = null; } } if (abort) { if (resultRecord != null) { resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } // We pretend to the caller that it was really started, but // they will just get a cancel result. setDismissKeyguard(false); ActivityOptions.abort(options); return ActivityManager.START_SUCCESS; } ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified, this); if (outActivity != null) { outActivity[0] = r; } final ActivityStack stack = getFocusedStack(); if (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid) { if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { PendingActivityLaunch pal = new PendingActivityLaunch(r, sourceRecord, startFlags, stack); mService.mPendingActivityLaunches.add(pal); setDismissKeyguard(false); ActivityOptions.abort(options); return ActivityManager.START_SWITCHES_CANCELED; } } if (mService.mDidAppSwitch) { // This is the second allowed switch since we stopped switches, // so now just generally allow switches. Use case: user presses // home (switches disabled, switch to home, mDidAppSwitch now true); // user taps a home icon (coming from home so allowed, we hit here // and now allow anyone to switch again). mService.mAppSwitchesAllowedTime = 0; } else { mService.mDidAppSwitch = true; } mService.doPendingActivityLaunchesLocked(false); err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options); if (allPausedActivitiesComplete()) { // If someone asked to have the keyguard dismissed on the next // activity start, but we are not actually doing an activity // switch... just dismiss the keyguard now, because we // probably want to see whatever is behind it. dismissKeyguard(); } return err; } |
调用函数mStackSupervisor.startActivityUncheckedLocked
调用函数ActiviyManagerService.checkGrantUriPermissionFromIntentLocked
调用函数ActivityStack.startActivityLocked
调用WindowManagerService.prepareAppTransition
调用函数ActivityStack.resumeTopActivityLocked
调用函数ActivityStack.startPausingLocked 开始pause当前Activity
调用函数ActivityManagerService.updateUsageStats 更新电池电量统计
调用函数mStackSupervisor.setFocusedStack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void setFocusedStack(ActivityRecord r) { if (r == null) { return; } if (!r.isApplicationActivity() || (r.task != null && !r.task.isApplicationTask())) { if (mStackState != STACK_STATE_HOME_IN_FRONT) { if (DEBUG_STACK || DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: mStackState old=" + stackStateToString(mStackState) + " new=" + stackStateToString(STACK_STATE_HOME_TO_FRONT) + " Callers=" + Debug.getCallers(3)); mStackState = STACK_STATE_HOME_TO_FRONT; } } else { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "setFocusedStack: Setting focused stack to r=" + r + " task=" + r.task + " Callers=" + Debug.getCallers(3)); final ActivityStack taskStack = r.task.stack; mFocusedStack = taskStack.isHomeStack() ? null : taskStack; if (mStackState != STACK_STATE_HOME_IN_BACK) { if (DEBUG_STACK) Slog.d(TAG, "setFocusedStack: mStackState old=" + stackStateToString(mStackState) + " new=" + stackStateToString(STACK_STATE_HOME_TO_BACK) + " Callers=" + Debug.getCallers(3)); mStackState = STACK_STATE_HOME_TO_BACK; } } } |
调用WindowManagerService.findFocusedWindowLocked
调用函数WindowManagerService.updateFocusedWindowLocked
调用函数:mStackSupervisor.allPausedActivitiesComplete
1.什么场景下需要使用HandlerThread
1). 比较耗时,不易于放在主线程中执行的操作(不考虑第2点使用其他线程方式也可以)
2). 有多个耗时操作需要后台执行(如果不嫌麻烦也可以考虑使用多个TThread)
2.HandlerThread的使用步骤
1). 创建HandlerThread对象
2). 执行start方法,启动HandlerThread的Looper循环
3). 在主线程中创建Handler对象并引用HandlerTherad的looper
4). 在Handler对象中加入各种消息的处理
5). 在需要的时候给步骤3创建的Handler对象发送消息
6). 不再需要HandlerThread的时候调用quit或者quitSafely停止HandlerThread
示例代码如下(没做各种错误处理,仅供参考):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
public class MainActivity extends AppCompatActivity { private Handler handler; private HandlerThread myHandlerThread ; private final int MSG_DOWNLOAD = 1; private final int MSG_DOWNLOAD2 = 2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myHandlerThread = new HandlerThread("test_thread"); // 这一步很重要 myHandlerThread.start(); handler = new Handler(myHandlerThread.getLooper()){ @Override public void handleMessage(Message msg) { OkHttpClient client = new OkHttpClient(); super.handleMessage(msg); switch (msg.what){ case MSG_DOWNLOAD: Request request = new Request.Builder() .url("http://baidu.com") .build(); try { Response response = client.newCall(request).execute(); Log.d("DOWNLOAD", response.body().string()); } catch (IOException e) { e.printStackTrace(); } break; case MSG_DOWNLOAD2: Request request1 = new Request.Builder() .url("http://bcoder.com") .build(); try { Response response = client.newCall(request1).execute(); Log.d("DOWNLOAD", response.body().string()); } catch (IOException e) { e.printStackTrace(); } break; } } }; ; } public void onDownload1Click(View v){ handler.sendEmptyMessage(MSG_DOWNLOAD); } public void onDownload2Click(View v){ handler.sendEmptyMessage(MSG_DOWNLOAD2); } public void onQuitClick(View v){ myHandlerThread.quit(); } } |
测试时多次点击Download1和Download2按钮,handler对象就会按点击的顺序多次下载网页baid.com和bcoder.com
3. 要不要像Thread那样写一个HandlerThread的子类?
完全没有必要,因为真正程序执行部分都在handler的消息处理里
4. quit和quitSafely的区别?
5. HandlerThread的其它特点
1). HandlerThread中的多个操作是串行按序执行的,即任务1\任务2\任务3....,所以如果你想尽早的获得运行结果,不建议使用HandlerThread这种方式
6. 测试代码下载
今天在使用switch控件的时候,发现他的宽度太大了,很丑
因为给它设置了android:track属性,用算定义图片来显示开和关的状态,以为是这个图片引起的,去掉后发现宽度没有变化,换了一堆属性switchPadding, thumbTextPadding,发现修改后都无效,设置固定宽度layout_width会造成switch的背景显示不全,看Switch的源码,发现有一个mSwitchMinWidth变量,并且该变量参与了宽度的计算,修改后发现起作用了,特此记录...
控制宽度的方法:
1.修改android:switchMinWidth属性
2.修改android:scaleX属性:也可以达到效果,但是缩小后左右会有空白(未深度研究)