本次趟地雷来源: https://blog.csdn.net/w359593616/article/details/50345109 。吐槽:这位兄台的排版实在是难以令我接受,只能将就着看看。我将这位兄台的文字重新排版一下,并且加入我在编译和调试的过程中 趟过的雷尽数标出

前言

我所使用的编译系统为Ubuntu 14.04.2。考虑到AllWinner A20的rom也是上古时代的产物了,用18.04可能需要重新配置很多东西,为了避免一些不必要的麻烦,故选择了这个版本的系统。

在系统位数选择上,推荐使用64位系统。当然也可以使用32位系统,不过会有一些限制,比如说 12.04 的32位系统默认最大支持8个cpu核心(8c16t只能利用8个核心),内存大小也有限制,不能进行打包等等。之前我就是因为盲信rom是32位的,就应该用32位的ubuntu进行编译,结果到后面编译成功后准备打包的时候发现打包程序居然是个64位的程序。。。

搭建编译环境

安装必要的依赖

下面是基础的依赖:

root@ubuntu: ~# sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev  gcc-multilib  g++-multilib  libc6-dev-i386  lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip
root@ubuntu: ~# sudo apt-get install bison texinfo u-boot-tools flex libswitch-perl

链接一个so文件,不链接好像也没啥问题:(32位系统可能不需要这个操作)

sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so

安装JDK 1.6

不要问为什么要jdk1.6,没有为什么,就是需要jdk1.6

这个jdk1.6也是相当古老了,在Oracle网站上找起来很费劲,不过在另外一篇博客的文章里找到了下载链接:http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase6-419409.html#jdk-6u41-oth-JPR

下载下来的文件是个bin文件,这里以 jdk1.6.0_45 为例,添加执行权限后直接在终端中执行,jdk会被解压到当前目录中,接下来就是把它放到一个找得到的位置,比如/usr/lib/jvm/jdk1.6.0_45,修改权限为755,所有者和所有人群组为root

然后修改/etc/profile,追加以下内容到文件末尾。注意:只要改profile即可,不要去修改/etc/environment,因为这个 environment 万一修改出错会导致开机卡登录界面,tty中找不到某些命令等问题(本质上是PATH路径损坏),修复起来会很麻烦

export JAVA_HOME=/usr/lib/jvm/jdk1.6.0_45
export JRE_HOME=$JAVA_HOME/jre
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/:$JRE_HOME/lib:$CLASEEPATH

请不要照抄那位兄台的export,要抄也要抄我提供的。因为他的排版格式问题,导致export后面的空格存在异常,不能被shell识别

接下来配置java,全程root权限执行:

#!/bin/bash
JAVA_PATH=/usr/lib/jvm/jdk1.6.0_45
update-alternatives --install /usr/bin/java java $JAVA_PATH/bin/java 300
update-alternatives --install /usr/bin/javac javac $JAVA_PATH/bin/javac 300
update-alternatives --install /usr/bin/jar jar $JAVA_PATH/bin/jar 300
update-alternatives --install /usr/bin/javah javah $JAVA_PATH/bin/javah 300
update-alternatives --install /usr/bin/javap javap $JAVA_PATH/bin/javap 300
update-alternatives --install /usr/bin/javadoc javadoc $JAVA_PATH/bin/javadoc 300
update-alternatives --config java
update-alternatives --config javac
ln -s $JAVA_PATH/bin/jar  /bin/jar
ln -s $JAVA_PATH/bin/java  /bin/java
ln -s $JAVA_PATH/bin/javac  /bin/javac
ln -s $JAVA_PATH/bin/javah  /bin/javah
ln -s $JAVA_PATH/bin/javadoc  /bin/javadoc

配置Swap分区

为了保证能成功编译,需要调整swap分区的大小。

以M为单位查看swap的大小

free -m

在当前用户home目录创建一个2GB大小的swap文件

dd if=/dev/zero of=~/swapfile bs=1024 count=2000000

建立交换分区

mkswap ~/swapfile

转化并激活swap分区文件

swapon swapfile

取消分区

swapoff ~/swapfile

替换gcc和g++

更新:咨询了厂商,得到的回复是14.04中不需要替换!替换之后反而会出现各种编译不过去的问题,所以这个到底要不要替换,看个人,可以多尝试尝试,记得做好备份,万一不行可以回档重来

gcc和g++版本问题,需要降为4.4 (不知道是否必须要这样做,反正先这样做着),下载完成之后替换系统原有的链接至4.4的版本

apt-get install gcc-4.4
apt-get install g++-4.4
cd /usr/bin
mv gcc gcc.bak
ln -s gcc-4.4 gcc
mv g++ g++.bak
ln -s g++-4.4 g++
# 查看两者版本
gcc -v
g++ -v

最后还需要安装:

apt-get install g++-4.4-multilib
apt-get install libc6-dev-i386

下载源码

我这边源码是合作厂商基于AllWinner A20的源码定制过后的版本,不过和网络上流传的版本基本一致,源码中分为android文件夹和lichee文件夹两个目录。

开始编译

编译lichee

这里有个小技巧,在改动必要文件之后,开始编译之前,利用git命令创建个本地仓库,将所有文件添加进版本控制。万一编译出了什么问题,可以通过git将文件夹中的内容完全重置回最初的状态,这样就省的去clean的工作了

接下来开始编译:

cd lichee
./build.sh -p sun7i_android

中途过程中可能会跳出若干选项,比如选择wifi驱动,3g/4g模块驱动等,按照实际需求进行选择即可

若看到以下输出,即表示lichee编译成功:

INFO: build u-boot OK.
INFO: build rootfs ...
INFO: skip make rootfs for android
INFO: build rootfs OK.
INFO: build lichee OK.

编译android

进入源码的另一个目录android,然后执行以下命令:

cd android
source build/envsetup.sh

注意,不要直接执行 ./build/envsetup.sh,据说会有问题

lunch
admin@ubuntu:android$ lunch

You're building on Linux

Lunch menu... pick a combo:
     1. full-eng
     2. full_x86-eng
     3. vbox_x86-eng
     4. full_mips-eng
     5. full_grouper-userdebug
     6. full_tilapia-userdebug
     7. mini_armv7a_neon-userdebug
     8. mini_armv7a-userdebug
     9. mini_mips-userdebug
     10. mini_x86-userdebug
     11. full_maguro-userdebug
     12. full_manta-userdebug
     13. full_toroplus-userdebug
     14. full_toro-userdebug
     15. sugar_evb-eng
     16. sugar_ref001-eng
     17. sugar_standard-eng
     18. wing_evb_v10-eng
     19. full_panda-userdebug

Which would you like? [full-eng]

根据自己开发平台选择方案,接下来的操作是 “拷贝内核和模块到Android中”,据传编译内核 一般需要接近1个小时???完成后将会在out/target/product/sun7i-xxx/目录下生成boot.img、recovery.img、system.img三个文件镜像。Boot.img包括kernel和ramdisk,system.img为系统文件

extract-bsp
make clean
make -j8

如果编译环境没有什么大的问题的话,应该就能编译成功。如果出现make提示源码中有什么东西找不到,请先确认源码是否完整,其次就是编译环境问题了。如果提示少了什么依赖,就按照提示安装即可

打包镜像

用pack命令打包出编译好的镜像文件

pack

完成后会提示你,出来的镜像文件是保存在哪里的

编译android过程中出现的各种问题

make: *** No rule to make target `out/target/product/sugar-ref001/obj/STATIC_LIBRARIES/libv8_intermediates/export_includes', needed by `out/target/product/sugar-ref001/obj/SHARED_LIBRARIES/libwebcore_intermediates/import_includes'.  Stop.

这就是降级了gcc和g++后使用多线程编译可能会出现的奇葩问题。这种情况下,使用单线程make不会有这个错误。如果不嫌编译慢,可以慢慢等。


Can't Locate Switch.pm in @INC (you may need to install the Switch module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr / Local/share/perl/5.18.2 /usr/lib/perl5 /usr /share/perl5 /usr/Lib/perl/5.18 /usr /share/perl/5.18 /usr/Local/lib/site_ perl .) at external/webkit/Source/WebCore/make -hash- tools.pl line 23.
BEGIN failed-- compilation aborted at external/webki t/Source /WebCore /make-hash- tools.pl line 23.
target Generated: libwebcore <= external/webkit/ Sour ce/WebCore / dom/make_ names. pl
make: *** [out/target/product/sugar-ref001/obj/STATIC LIBRARIES/libwebcore intermediates/Source/WebCore/html/DocTypestr ings.cpp] Error 2

缺少了 libswitch-perl ,安装上即可解决问题


external/dbus/bus/activation.c:26:20: fatal error: config.h: No such file or directory
compilation terminated.

这个还是编译环境问题,具体是什么情况我也没搞清楚

解决方案参考: 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,因为这个可以说是通用的