解决方案参考: https://forum.xda-developers.com/pixel-c/help/device-resource-busy-t3449240

正常将只读的/system分区变成可读写是用下面这个命令:

mount -o remount,rw /system

一般情况下,/system就变成可读写了。但是在某些设备上,会提示Device or resource busy错误。这个时候只需要将-o的两个参数顺序调换一下即可,也就是这样:

mount -o rw,remount /system

本文的创作参考了很多前辈们的博文。没有这些前辈们,估计现在我还在ffmpeg的坑里面打转。。

首先要感谢这位前辈的博文:https://www.cnblogs.com/tplusy/p/11012149.html。本文的思路就是源自于Ta。虽然Ta是在Windows上进行处理的,但是却给我了启发。好了,下面开始正题。

事前工作

准备一台运行Ubuntu的电脑/虚拟机。我这边使用的是vmware虚拟机,系统为ubuntu 18.04 Server。同时安装好必要的依赖。

下载NDK

这里下载NDK。我这边选择了NDK r19c的版本。下载完成后解压到目录中备用。

下载FFmpeg源码

FFmpeg的官网地址: https://ffmpeg.org/download.html 。找到右下角的source code,通过git clone下来,然后可以直接进行使用,或者也可以直接下载打包好的tar.bz2,但是建议还是通过git去clone源码。我这边clone后选择的是切换到release/4.2的分支上进行编译操作。

题外废话

最开始我用了r13b的NDK,打算用gcc去进行编译。后来发现搞不灵光,总是有莫名其妙的错误。后来参考前辈的博文,就去下了r19c的NDK,用llvm的clang进行编译。说实话clang确实方便,可以完全不用改动FFmpeg的源码,而且FFmpeg也已经在configure文件中加入了android平台的支持。经研究,当前的分支release/4.2中,如果编译平台为android,则默认使用clang进行编译。

编写编译脚本

我用的脚本如下(llvm的clang编译),仅供参考:

#!/bin/bash

#Android System API Level,你要运行在什么系统上,就填写系统API Level
#但是这个API Level必须要能够在NDK中找得到,详见下面的ANDROID_CROSS_PREFIX
API_LEVEL=23

#设置ndk目录
NDK=<改成你的ndk路径>/android-ndk-r19c

#llvm toolchain路径。linux下是linux-x86_64,windows下则是windows开头的。
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64

#sysroot 这个一定要设置成 ndk的llvm 路径下的 sysroot 
#这里有个坑要注意,sysroot文件夹在r19c后才存在,r18b中是没有的!
SYSROOT=$TOOLCHAIN/sysroot

#ar nm 的prefix。这里一定要保证和编译的系统位数保持一致
PLATFORM=aarch64-linux-android

#ASM 路径, 同上必须是llvm 目录下的 asm
#说实话,我不知道为啥要ASM。。
ASM=$SYSROOT/usr/include/$PLATFORM

#完整的 cross prefix
CROSS_PREFIX=$TOOLCHAIN/bin/$PLATFORM-

#专门给ndk clang/clang++ 的 cross prefix
ANDROID_CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android$API_LEVEL-

#临时文件目录
TMPDIR="./temp"

#CPU架构
#64位arm:aarch64
#32位arm:armv7-a
#64位intel/amd:x86_64
#32位intel/amd:x86
ARCH=aarch64

#操作系统
OS=android

#安装位置
PREFIX=~/ffmpeg_out/android/$ARCH

#额外C参数
ADDI_CFLAGS=""

#这里面不能写注释,否则会报错
./configure \
--prefix=$PREFIX \
--enable-cross-compile \
--cross-prefix=$CROSS_PREFIX \
--target-os=$OS \
--arch=$ARCH \
--pkg-config=$(which pkg-config) \
--cc=${ANDROID_CROSS_PREFIX}clang \
--cxx=${ANDROID_CROSS_PREFIX}clang++ \
--disable-asm \
--disable-x86asm \
--disable-stripping \
--sysroot=$SYSROOT \
--fatal-warnings \
--enable-gpl \
--enable-version3 \
--enable-nonfree \
--disable-ffplay \
--disable-ffprobe \
--enable-pic \
--enable-jni \
--enable-shared \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-decoder=hevc_mediacodec \
--enable-decoder=mpeg4_mediacodec \
--enable-decoder=vp8_mediacodec \
--enable-decoder=vp9_mediacodec \
--extra-cflags="-Os -fpic -I$ASM -isysroot $SYSROOT" \
--extra-ldflags="$ADDI_LDFLAGS"
make clean
make -j 4
make install

因为我这边的测试平台是A64的开发板,cpu是64位的,系统是android6,所以我这边选择编译的是aarch64平台(性能更好),同时手动指定了cc和cxx。为什么要手动指定?请看下面:

set_default target_os
if test "$target_os" = android; then
    cc_default="clang"
fi

ar_default="${cross_prefix}${ar_default}"
cc_default="${cross_prefix}${cc_default}"
cxx_default="${cross_prefix}${cxx_default}"
nm_default="${cross_prefix}${nm_default}"
pkg_config_default="${cross_prefix}${pkg_config_default}"

cc和cxx默认和ar,nm等使用同一个cross_prefix。而在NDK中,他俩的prefix和其他的可是不一样的,cc和cxx的文件名后面有个api level的数字。这就会导致找不到cc和cxx。为了解决这个问题,这位前辈在configure文件中添加了一个单独的clang的prefix:点我去看 。他这么做确实可以,但是需要改动源码,比较麻烦。所以我选择手动指定,偷懒成功。

编译

脚本弄好后,编译就很简单了,直接执行脚本,然后看着屏幕不停的滚动,然后惬意的等一小会儿就好了。完成之后就可以到输出文件夹里面找到ffmpeg和一堆so文件,把so拷贝到/system/lib64中,ffmpeg拷贝到/system/bin下面,修正权限,然后就可以直接执行ffmpeg啦!

后记

在android 4.4之后,添加了新的保护机制,可执行文件必须是采用PIE编译的,即必须为地址无关代码,否则会提示:error: only position independent executables (PIE) are supported.。但是我上述的编译脚本中并未加上-fPIE -pie,却也能在A64开发板上运行。这个问题有待后续在别的平台上进行考证。 已经确认编译器会自动加上-fPIE fpie选项

遇到的坑

坑一号:makefile找不到

表现为类似下面的情况:

./android_config.sh: line 36: --enable-shared: command not found
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:91: /libavutil/Makefile: No such file or directory
Makefile:91: /ffbuild/library.mak: No such file or directory
Makefile:93: /fftools/Makefile: No such file or directory
Makefile:94: /doc/Makefile: No such file or directory
Makefile:95: /doc/examples/Makefile: No such file or directory
Makefile:160: /tests/Makefile: No such file or directory
make: *** No rule to make target `/tests/Makefile'. Stop.
Makefile:2: ffbuild/config.mak: No such file or directory
Makefile:40: /tools/Makefile: No such file or directory
Makefile:41: /ffbuild/common.mak: No such file or directory
Makefile:91: /libavutil/Makefile: No such file or directory
Makefile:91: /ffbuild/library.mak: No such file or directory
Makefile:93: /fftools/Makefile: No such file or directory
Makefile:94: /doc/Makefile: No such file or directory
Makefile:95: /doc/examples/Makefile: No such file or directory
Makefile:160: /tests/Makefile: No such file or directory

这位前辈对于这个问题的解决方法不对,我在这个问题上研究了好久才发现根本不是那么一回事,希望不要再有人被他坑了(他总结的第2条): https://www.laoyuyu.me/2019/05/23/android/clang_compile_ffmpeg/

这个问题产生的原因是编译脚本路径或者脚本执行的路径不正确。即使你在脚本里面cd到了ffmpeg的文件夹中也没用,非得把编译脚本放进ffmpeg的文件夹中才行。也许还有其他的办法,但是最省事的还是放一块儿吧。。。

坑二号:xxxxxx is unable to create an executable file

  1. 检查是否设置了临时目录。
  2. 检查ndk版本,android官方从r18b开始,已经移除了gcc这个编译工具,推荐clang。详情见ndk r18b修订内容

坑三号:/android_config.sh: line xx: xxxxx No such file or directory

configure命令之后如果使用了反斜杠进行换行的话,是不可以在其中添加注释的。把注释删除即可。

坑四号:xxxxx : not executable: 64-bit ELF file

这位前辈的博文:https://blog.csdn.net/u010651541/article/details/50177867,里面提到了关于64-bit ELF的问题,其实并不仅仅是Ta所描述的- c 的问题。如果编译的平台是x86_64的程序,放到aarch64的Android上去运行也是会提示这个错误的。

还有更多的坑,等待被发掘……

一、一般在代码中的调用,要么通过id找到,要么直接new出来:

①、调用setTextSize(<数值单位>,<值>)

这里的数值单位可以为下列的几种:

TypedValue.COMPLEX_UNIT_PX
TypedValue.COMPLEX_UNIT_DIP
TypedValue.COMPLEX_UNIT_SP

当然还有不常用的,比如

TypedValue.COMPLEX_UNIT_PT

1 .如果直接使用固定的数值,那么该数值的单位则由上述的几种枚举决定,比如:

setTextSize(TypedValue.COMPLEX_UNIT_SP,16);//就是设置为16sp的大小

2 .如果使用资源文件中的数值,那么请注意,数值单位 要设置为 COMPLEX_UNIT_PX,比如:

setTextSize(TypedValue.COMPLEX_UNIT_PX,getResources().getDimensionPixelSize(R.dimen.你的资源);

 原因如下

* 获取dimension的方法有几种,区别不大
* 共同点是都会将dp,sp的单位转为px,px单位的保持不变
*
* getDimension() 返回float
* getDimensionPixelSize 返回int 小数部分四舍五入
* getDimensionPixelOffset 返回int,但是会抹去小数部分

getDimensionPixelSize在要求不高的情况下也可以替换为getDimensionPixelSize或者getDimensionPixelOffset,具体根据业务需求决定

②、调用setTextSize(<Size>):

这个方法不带单位,Doc的描述如下:

Set the default text size to the given value, interpreted as “scaled pixel” units. This size is adjusted based on the current density and user font size preference.

大意就是,Size单位是px且当设置为某个值的时候,最后会乘一个destiny后显示出来

没例子,也不建议用

二、自定义控件中调用:

①、使用paint直接画TextView:

调用paint.setTextSize(<px值>)即可

②、在declare-styleable中定义size,在xml中设置size,代码中通过TypedArray取出来:

这是比较常见的情况,此时通过调用TypedArray的对象调用getDimension,getDimensionPixelSize 或者getDimensionPixelOffset 方法来获取。同样的,返回结果是以px为单位的

然后,同(一)里面的做法,设置大小即可

继续阅读

一直以来都被这个onNewIntent(Intent intent)的用法所困扰,直到近期才理解了其用法!

简而言之,如果要通过intent来跳转一个之前已存在/不存在的Activity,onNewIntent(Intent intent)则有可能被调用。

什么意思呢,请看以下解释:

①,如果IntentActivity处于任务栈的顶端,也就是说之前打开过的Activity,现在处于onPause、onStop 状态的话,其他应用再发送Intent的话,执行顺序为:
onNewIntent,onRestart,onStart,onResume。。。

②,系统可能会随时杀掉后台运行的 Activity ,如果这一切发生,那么系统就会调用 onCreate 方法,而不调用 onNewIntent 方法

所以,如果要保证该Activity一定要处理intent,一个好的解决方法就是在 onCreate 和 onNewIntent 方法中调用同一个处理数据的方法。

另外,一般情况下会让这个Activity的启动模式设置为singleTask,以防止实例化多个Activity(如此基本可以让onNewIntent得到调用)

技巧:在onNewIntent里面调用setIntent(Intent intent)可以覆盖原有的intent,但是要慎用

举个简单的例子:Activity A是有5个Tab的FragmentActivity,Activity B是一个普通Activity,包含一个按钮button1。A中的Tab1里面有个button2,点击button2跳转到B。如果点击B中的button1需要跳转回Activity A且让Tab选中在第5个,此时可以将A设置为singleTask,button1点击事件直接设置intent的extra然后调用startActivity(Intent intent),然后在A的onNewIntent中处理。最好在onCreate中也处理一下intent,防止A被销毁后不走onNewIntent

参考:https://my.oschina.net/xsjayz/blog/138447

support-v4中的fragment在现在看来显得有些老旧,因为现在的手机绝大部分都是4.X系统以上了,所以v4包中的fragment其实可以甩掉了。本文教你如何从v4包的fragment转换到原生fragment

首先你要确定一件事情——你想要你的app兼容到哪个系统。如果你想放弃过时的2.X系统,那么就请继续往下看吧

从v4的fragment转换到原生的fragment其实并不困难,有几点需要注意:

1)导入的包:

请将android.support.v4.app.fragment替换为android.app.fragment;

同样的,FragmentManager请替换为android.app.FragmentManager,以及FragmentTransaction

2)修改Activity:

一般情况下继承AppCompatActivity即可,这个也是AS默认的Activity继承类

3)如果使用了viewpager的话,请不要改动viewpager的引用

4)调用viewpager的setAdapter方法的时候,需要实现一个继承于android.support.v13.app.FragmentStatePagerAdapter的适配器,而这个类的引用方式需要引入v13的包:

compile ‘com.android.support:support-v13:23.3.0’

基本就是以上注意点,除此之外的用法均和support-v4中的一致。如果还有其他注意要点的话我会继续补充~

在Android5.0以前的版本中,可以通过简单的反射机制来直接控制手机网络的打开与关闭:

ConnectivityManager connectivityManager =
        (ConnectivityManager) pContext.getSystemService(Context.CONNECTIVITY_SERVICE);
Method setMobileDataEnabl;
try {
    setMobileDataEnabl = connectivityManager.getClass().getDeclaredMethod("setMobileDataEnabled", boolean.class);
    setMobileDataEnabl.invoke(connectivityManager, pBoolean);
} catch (Exception e) {
    e.printStackTrace();
    Log.e("MobileUtil", "移动数据设置错误:" + e.toString());
}

但是,在Android5.0版本及以上,这方法就失效了,于是在StackOverFlow上面有大神提出一个解决方案,前提是手机必须能获取到Root权限:

private static void executeCommandViaSu(Context context, String option, String command) {
    boolean success = false;
    String su = "su";
    for (int i=0; i < 3; i++) {
        // Default "su" command executed successfully, then quit.
        if (success) {
            break;
        }
        // Else, execute other "su" commands.
        if (i == 1) {
            su = "/system/xbin/su";
        } else if (i == 2) {
            su = "/system/bin/su";
        }
        try {
            // Execute command as "su".
            Runtime.getRuntime().exec(new String[]{su, option, command});
        } catch (IOException e) {
            success = false;
            // Oops! Cannot execute `su` for some reason.
            // Log error here.
        } finally {
            success = true;
        }
    }
}
public static void setMobileNetworkfromLollipop(Context context,boolean ii) throws Exception {
    String command = null;
    int state=0;
    if(ii){
        state=1;
    }
    try {
        String transactionCode = getTransactionCode(context);
        // Android 5.1+ (API 22) and later.
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            // Loop through the subscription list i.e. SIM list.
            for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) {
                if (transactionCode != null && transactionCode.length() > 0) {
                    // Get the active subscription ID for a given SIM card.
                    int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId();
                    // Execute the command via `su` to turn off
                    // mobile network for a subscription service.
                    command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + state;
                    executeCommandViaSu(context, "-c", command);
                }
            }
        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
            // Android 5.0 (API 21) only.
            if (transactionCode != null && transactionCode.length() > 0) {
                // Execute the command via `su` to turn off mobile network.
                command = "service call phone " + transactionCode + " i32 " + state;
                executeCommandViaSu(context, "-c", command);
            }
        }
    } catch(Exception e) {
        // Oops! Something went wrong, so we throw the exception here.
        throw e;
    }
}
private static String getTransactionCode(Context context) throws Exception {
    try {
        final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        final Class<?> mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName());
        final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony");
        mTelephonyMethod.setAccessible(true);
        final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager);
        final Class<?> mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName());
        final Class<?> mClass = mTelephonyStubClass.getDeclaringClass();
        final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled");
        field.setAccessible(true);
        return String.valueOf(field.getInt(null));
    } catch (Exception e) {
        // The "TRANSACTION_setDataEnabled" field is not available,
        // or named differently in the current API level, so we throw
        // an exception and inform users that the method is not available.
        throw e;
    }
}

当代码中调用setMobileNetworkfromLollipop方法时,系统会提示应用要获取Root权限,其实这个方法的本质就是在Linux的终端中输入了svc data enable。。。 没有Root的机器是暂时没有解决办法的,因为只有root身份才能执行svc data enable。不知道这是不是Google故意把那个非公开api去掉了,不过回过头想想,不能直接使用这个方法也是为了考虑到系统的安全性,毕竟这个被滥用后会是一件非常麻烦的事 如果有别的方案可以开启数据连接的话欢迎提出来

对于android 3.0以下 的系统,使用Notification.Builder来创建通知;android3.0以上的系统,推荐使用NotificationCompat.Builder,当然也能使用Notification.Builder,这个NotificationCompat在v4包中。不推荐直接使用Notification来创建通知。

首先要获取NotificationManager:(这个无论什么版本的系统都是需要的)

NotificationManager nm = (NotificationManager)mContext.getSystemService(NOTIFICATION_SERVICE);

接下来就是创建一个通知

3.0以上系统写法:

NotificationCompat.Builder mBuilder;

mBuilder = new NotificationCompat.Builder(mContext);

3.0以下的写法:

Notification.Builder mBuilder;

mBuilder = new Notification.Builder(mContext);

其实写法都是大同小异的

有几个常用的设置属性的方法:

setContentTitle(String title) //设置通知的标题

setContentText(String content) //设置通知的内容

setTicker(String ticker)//设置通知出现的的时候显示在顶部/底部通知栏的提示消息

setSmallIcon(int resIcon)//设置通知的图标(经测试,此项必选,否则通知将不显示

setWhen(System.currentTimeMillis())//设置发送通知的时间为当前时间

setContentIntent(Intent contentIntent)//设置点击通知的时候要发送的pendingIntent

setAutoCancel(boolean cancelable)//设置通知是否能被清除

setProgress(int max,int progress,boolean indeterminate)//如果设置这个属性的话通知中就会显示一个进度条,进度值为progress,最大值为max

设置完毕后调用build()即可创建通知,该方法将返回一个Notification的实例

通知创建出来后需要通过NotificationManager来发送:

notify(int uniqueId,Notification notification);//uniqueId是全app的一个唯一id值,如果要清除或者更新这个通知的话需要将uniqueId设置为相同的值

如果要清除这个通知,调用这个方法:

cancel(int uniqueId);

(2015年8月8日补充)

其实在平时的开发中,为了能兼容低版本的系统,同时又为了减少代码中对版本号的判断,可以考虑直接使用NotificationCompat.Builder,因为这个可以说是通用的