最近开发一个android电视上运行的软件,由于需要长时间运行,所以需要保证稳定性,以及在意外情况下能自动重启运行。要直接让应用程序本身保证无bug还是有一定难度,一般情况是外加守护程序(daemon)或者叫做看门口。以往的应用在pc端的应用中就是这么使用的,能达到需要的长期运行的效果。同样的方法也就确定在android上这么使用了。确定方案开始干。
刚开始想着平移windows的守护程序,但是问题就随之而来。windows上可以直接OpenProcess 、共享内存交互、获取进程内存情况等各种信息;到android,这些都无对应功能函数。所以首先看看其他应用中都是使用什么方式实现守护程序功能。首先查到的是java的方式。
android各种 前台后台进程介绍这里就不多说了。
1、使用java android的 Service 方式
结果,模拟器可以用,手机可以用,android电视上无法拉起程序。查找各种权限原因等,都无法解决。更有病的是电视无法adb调试,使用的是海信的最近1-2年的型号。无法调试这大大增加了解决问题的难度。和售后沟通之后,答复是他们的电视都取消了这个功能,所以各个开发朋友以后得注意了要做产品,海信的首先必须不能采购。以后做电视二次开发类似的产品,都必须提前售后咨询好,要不是后患无穷。
各种尝试几天后,java这个方式只能放弃。
2、linux原生接口。
再次搜索之后得到有另一个途径,使用linux的fork方式。android本身仍然是linux系统,但是很多被阉割或者限制了,不过从搜索的结果看还是能实现的。反正也没有其他好办法,只能这么尝试。
如果使用daemon方式就是两个程序,应该可以调用起来。但是作为普通apk程序,运行级别低,容易被系统kill。所以daemon就得运行级别高,最好是linux的bin程序。做bin程序也有很大麻烦:首先是安装包里面如何添加bin程序打包;其次是bin程序所在目录要能被访问。所以这个完整方式只能靠后了,而简单测试后使用直接apk自身fork的方式实现。(应用程序本身是ndk+qt开发)。
大致daemon函数如下:
int CreateDaemon()
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD, &sa, NULL) < 0)
{
return -1;
}
int pid;
if(pid=fork())
{
// return 0;
exit(0);//是父进程,结束父进程
}
else if(pid< 0)
exit(1);//fork失败。退出
//是第一子进程,后台继续执行
/* setsid();//第一子进程成为新的会话组长和进程组长
//struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD, &sa, NULL) < 0)
{
return -1;
}
//并与控制终端分离
if(pid=fork())
exit(0);//是第一子进程,结束第一子进程
else if(pid< 0)
exit(1);//fork失败,退出
*/
int i=0;
qint64 nPid = nHostPid;
while ( i++<10 )
{
usleep(5*1000*1000);
//continue;
//
//if (nPid > 0)
{
QString strKill;
//am stop org.qtproject.TestDaemon
// strKill = strKill.sprintf("kill -9 %d", (int)nPid);
// strKill = strKill.sprintf("am force-stop org.qtproject.TestDaemon");
// QProcess::execute(strKill);
//am force-stop org.qtproject.example.testMediaSdk
strKill = strKill.sprintf("am force-stop org.qtproject.example.testMediaSdk");
QProcess::execute(strKill);
}
usleep(5*1000*1000);
//
QString strProgram = "am";
QString strCmp;
strCmp = "start --user 0 -n org.qtproject.example.testMediaSdk/org.qtproject.qt5.android.bindings.QtActivity";
QStringList arrArgs;
arrArgs += "start";
arrArgs += "--user";
arrArgs += "0";
arrArgs += "-n";
//arrArgs += "org.qtproject.example.testMediaSdk/org.qtproject.qt5.android.bindings.QtActivity";
arrArgs += "org.qtproject.TestDaemon/org.qtproject.qt5.android.bindings.QtActivity";
// if ( QProcess::startDetached(strProgram, arrArgs, QString(), &nPid) )
// {
// qDebug("%s: start process ok\n");
// }
// else
// {
// qDebug("%s: start process false\n");
// }
QStringList arrArgs1;
arrArgs1 += "start";
arrArgs1 += "--user";
arrArgs1 += "0";
arrArgs1 += "-n";
arrArgs1 += "org.qtproject.example.testMediaSdk/org.qtproject.qt5.android.bindings.QtActivity";
// arrArgs += "org.qtproject.TestDaemon/org.qtproject.qt5.android.bindings.QtActivity";
if ( QProcess::startDetached(strProgram, arrArgs1, QString(), &nPid) )
{
qDebug("%s: start process ok\n");
}
else
{
qDebug("%s: start process false\n");
}
//
}
exit(0);
return 0;
}
这里面主要为测试代码,这个是循环启动10次
其中测试 kill 命令的 是没成功的。
有几个问题:
1、fork需要两次,这样摆脱原始进程的影响,实际测试过程中不同的手机或者电视,有的如果只是fork一次,daemon确实会因为原始进程退出而退出。
2、这里使用的是qt的进程启动命令,其实就是封装了2次fork过程。这里如果不使用detach方式启动程序,也会有关联被杀死的问题。
3、文件锁,这里没有加入。在实际应用中添加了,这个是用于判断主程序是否退出的通信方式。这个也遇到了不少坑
1)电视上 android很多目录无法访问,这样就无法创建文件锁。需要找到一个可读写文件的目录。
2)如果不用文件锁,使用mutex+mmap方式(共享内存中的锁)。也不行,因为要跨进程,mmap也得使用命名文件。
3)共享内存,android禁用了linux的共享内存
4)命名的semaphore, 进程异常退出无法正确计数;另外,android也不支持命名semaphore。
经过这些折腾,守护程序能在电视上正确的拉起程序。另外是程序如果异常退出,电视会弹框,手机上并不会。这个弹框要等5-10分钟才会自动消失,不用理会。
(android7/8 以上的版本测试)程序主要功能为连接主服务,播放视频、图片、文字等信息。属于信息展示系统/信息发布系统。进一步需要将daemon独立开。